Compare commits

...

41 Commits

Author SHA1 Message Date
Nex
ff41efba72 Bumped version 2022-04-05 21:46:38 +02:00
Nex
26e6a00bf5 Added new iOS version 2022-04-04 13:25:13 +02:00
Nex
9d61b9048c Fixed variable names mismatch and styling 2022-03-30 08:49:22 +02:00
tek
9950b3d6c2 Add appops dumpsys parser and modules 2022-03-30 01:16:22 +02:00
tek
e0d30ea990 Removes check for a deprecated Android setting 2022-03-29 18:37:56 +02:00
tek
293752f90a Merge branch 'main' of github.com:mvt-project/mvt 2022-03-28 20:12:17 +02:00
tek
ac1e5c29d3 Clarifies the backup path needed in the documentation 2022-03-28 15:38:20 +02:00
Nex
d868e6d9f0 Merge pull request #259 from mlowdi/configuration_profiles_fix
base64 encoding fixes in ConfigurationProfiles module
2022-03-28 14:08:22 +02:00
Martin L. Fällman
f5cb7f06e1 Fix for missing base64 encoding of MDM certificate data in JSON output 2022-03-25 20:36:30 +01:00
Martin L. Fällman
5ce8035820 Add Sublime Text project files to .gitignore 2022-03-25 20:16:20 +01:00
Donncha Ó Cearbhaill
e3a8bde150 Fix path error when relative '.' used as backup source directory 2022-03-20 15:56:13 +01:00
Nex
d6af7c8cca Updating flake8 config and fixed some violations 2022-03-18 11:10:06 +01:00
Nex
6584d8232c Fixed bug in bugreport packages parser 2022-03-16 10:20:53 +01:00
Nex
3487078c03 Added flake8 configuration file 2022-03-15 13:36:03 +01:00
Nex
bc5d386be7 Bumped version 2022-03-15 11:19:22 +01:00
Nex
03efc8494b Added new iOS version 2022-03-15 11:19:05 +01:00
Nex
0b3f529cfa Bumped version 2022-03-14 10:22:29 +01:00
Nex
9bdef6ede4 Fixing spacing 2022-03-10 11:35:49 +01:00
Nex
fc9a27d030 Sorted imports 2022-03-10 11:33:54 +01:00
tek
f5f3660d82 Updates the documentation 2022-03-08 14:17:41 +01:00
Tek
712f5bcb9b Merge pull request #251 from mvt-project/feature/read-sms-adb-backup
Add initial implementation of SMS extraction using ADB
2022-03-05 23:27:55 +01:00
Donncha Ó Cearbhaill
ac26aa964a Fix exception with bad password 2022-03-04 17:24:26 +01:00
Donncha Ó Cearbhaill
be511dcb51 Refactor SMS ADB code to use backup functions 2022-03-04 17:06:10 +01:00
Donncha Ó Cearbhaill
b44c67e699 Refactor some of the decryption code 2022-03-04 17:04:32 +01:00
tek
a4d08f8f35 Replaces pyaes with cryptography and reorganize backup parser code 2022-03-04 15:05:10 +01:00
tek
6cc67f3c1d Fixes testing issue 2022-03-04 12:34:54 +01:00
tek
0d5377597f Merge branch 'main' into feature/read-sms-adb-backup 2022-03-04 12:30:45 +01:00
tek
86c79075ff Reorganise code for backup modules 2022-03-04 10:10:56 +01:00
tek
9940b1d145 Adds test of the check-backup command 2022-03-01 18:54:34 +01:00
tek
b07fb092aa Adds tests for SMS module 2022-03-01 13:11:50 +01:00
tek
639c163297 Adds partial compression support in Android Backup parsing 2022-02-23 16:18:45 +01:00
tek
8eb30e3a02 Improves android backup parsing for check-backup and check-adb 2022-02-23 15:07:13 +01:00
Donncha Ó Cearbhaill
cd0e7d9879 Fix syntax error with broken comment 2022-02-18 15:09:08 +01:00
Donncha Ó Cearbhaill
bdaaf15434 Add initial implementation of SMS extraction using ADB 2022-02-17 18:17:38 +01:00
tek
699824d9ff Adds iOS version 15.3.1 2022-02-11 12:25:53 +01:00
Nex
8cca78d222 Missing newline 2022-02-09 13:31:27 +01:00
Nex
57cbb0ed56 Fixed typo 2022-02-09 13:30:31 +01:00
Nex
e9cc6b3928 Fixed code styling and added missing check in adb getprop 2022-02-09 13:20:09 +01:00
tek
6d47d4d416 Adds warning for outdated iOS systems 2022-02-08 15:49:10 +01:00
tek
ed54761747 Adds warning if phone is outdated in getprop module 2022-02-07 17:28:01 +01:00
Nex
71c4ba799f Fixed help message for download-apks 2022-02-04 13:42:32 +01:00
51 changed files with 1276 additions and 123 deletions

10
.flake8 Normal file
View File

@@ -0,0 +1,10 @@
[flake8]
max-complexit = 10
max-line-length = 1000
ignore =
C901,
E265,
E127,
F401,
W503,
E226

5
.gitignore vendored
View File

@@ -133,4 +133,7 @@ dmypy.json
*~
# IDEA Dev Environment
.idea
.idea
# Sublime Text project files
*.sublime*

View File

@@ -11,18 +11,37 @@ That said, most versions of Android should still allow to locally backup SMS mes
Because `mvt-android check-backup` currently only supports checking SMS messages, you can indicate to backup only those:
```bash
adb backup com.android.providers.telephony
adb backup -nocompress com.android.providers.telephony
```
In case you nonetheless wish to take a full backup, you can do so with
```bash
adb backup -all
adb backup -nocompress -all
```
## Unpack the backup
Some recent phones will enforce the utilisation of a password to encrypt the backup archive. In that case, the password will obviously be needed to extract and analyse the data later on.
In order to unpack the backup, use [Android Backup Extractor (ABE)](https://github.com/nelenkov/android-backup-extractor) to convert it to a readable file format. Make sure that java is installed on your system and use the following command:
## Unpack and check the backup
MVT includes a partial implementation of the Android Backup parsing, because of the implementation difference in the compression algorithm between Java and Python. The `-nocompress` option passed to adb in the section above allows to avoid this issue. You can analyse and extract SMSs containing links from the backup directly with MVT:
```bash
$ mvt-android check-backup --output /path/to/results/ /path/to/backup.ab
14:09:45 INFO [mvt.android.cli] Checking ADB backup located at: backup.ab
INFO [mvt.android.modules.backup.sms] Running module SMS...
INFO [mvt.android.modules.backup.sms] Processing SMS backup file at
apps/com.android.providers.telephony/d_f/000000_sms_backup
INFO [mvt.android.modules.backup.sms] Extracted a total of 64 SMS messages containing links
```
If the backup is encrypted, MVT will prompt you to enter the password.
Through the `--iocs` argument you can specify a [STIX2](https://oasis-open.github.io/cti-documentation/stix/intro) file defining a list of malicious indicators to check against the records extracted from the backup by MVT. Any matches will be highlighted in the terminal output.
## Alternative ways to unpack and check the backup
If you encounter an issue during the analysis of the backup, you can alternatively use [Android Backup Extractor (ABE)](https://github.com/nelenkov/android-backup-extractor) to convert it to a readable file format. Make sure that java is installed on your system and use the following command:
```bash
java -jar ~/path/to/abe.jar unpack backup.ab backup.tar
@@ -33,17 +52,4 @@ If the backup is encrypted, ABE will prompt you to enter the password.
Alternatively, [ab-decrypt](https://github.com/joernheissler/ab-decrypt) can be used for that purpose.
## Check the backup
You can then extract SMSs containing links with MVT:
```bash
$ mvt-android check-backup --output /path/to/results/ /path/to/backup/
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 file at /path/to/backup/apps/com.android.providers.telephony/d_f/000000_sms_backup
16:18:39 INFO [mvt.android.modules.backup.sms] Extracted a total of
64 SMS messages containing links
```
Through the `--iocs` argument you can specify a [STIX2](https://oasis-open.github.io/cti-documentation/stix/intro) file defining a list of malicious indicators to check against the records extracted from the backup by MVT. Any matches will be highlighted in the terminal output.
You can then extract SMSs containing links with MVT by passing the folder path as parameter instead of the `.ab` file: `mvt-android check-backup --output /path/to/results/ /path/to/backup/` (the path to backup given should be the folder containing the `apps` folder).

View File

@@ -190,7 +190,7 @@ If indicators are provided through the command-line, they are checked against th
Backup: :material-check:
Full filesystem dump: :material-check:
This JSON file is created by mvt-ios' `Datausage` module. The module extracts records from a SQLite database located */private/var/wireless/Library/Databases/DataUsage.sqlite*, which contains a history of data usage by processes running on the system. Besides the network statistics, these records are particularly important because they might show traces of malicious process executions and the relevant timeframe. In particular, processes which do not have a valid bundle ID might require particular attention.
This JSON file is created by mvt-ios' `Datausage` module. The module extracts records from a SQLite database located */private/var/wireless/Library/Databases/DataUsage.sqlite*, which contains a history of network data usage by processes running on the system. It does not log network traffic through WiFi (the fields `WIFI_IN` and `WIFI_OUT` are always empty), and the `WWAN_IN` and `WWAN_OUT` fields are stored in bytes. Besides the network statistics, these records are particularly important because they might show traces of malicious process executions and the relevant timeframe. In particular, processes which do not have a valid bundle ID might require particular attention.
If indicators are provided through the command-line, they are checked against the process names. Any matches are stored in *datausage_detected.json*. If running on a full filesystem dump and if the `--fast` flag was not enabled by command-line, mvt-ios will highlight processes which look suspicious and check the presence of a binary file of the same name in the dump.

View File

@@ -3,14 +3,20 @@
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import getpass
import io
import logging
import os
import tarfile
from pathlib import Path
from zipfile import ZipFile
import click
from rich.logging import RichHandler
from mvt.android.parsers.backup import (AndroidBackupParsingError,
InvalidBackupPassword, parse_ab_header,
parse_backup_file)
from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_IOC,
HELP_MSG_LIST_MODULES, HELP_MSG_MODULE,
HELP_MSG_OUTPUT, HELP_MSG_SERIAL)
@@ -51,7 +57,7 @@ def version():
#==============================================================================
# Command: download-apks
#==============================================================================
@cli.command("download-apks", help="Download all or non-safelisted installed APKs installed on the device")
@cli.command("download-apks", help="Download all or only non-system installed APKs")
@click.option("--serial", "-s", type=str, help=HELP_MSG_SERIAL)
@click.option("--all-apks", "-a", is_flag=True,
help="Extract all packages installed on the phone, including system packages")
@@ -246,6 +252,44 @@ def check_bugreport(ctx, iocs, output, list_modules, module, bugreport_path):
def check_backup(ctx, iocs, output, backup_path, serial):
log.info("Checking ADB backup located at: %s", backup_path)
if os.path.isfile(backup_path):
# AB File
backup_type = "ab"
with open(backup_path, "rb") as handle:
data = handle.read()
header = parse_ab_header(data)
if not header["backup"]:
log.critical("Invalid backup format, file should be in .ab format")
ctx.exit(1)
password = None
if header["encryption"] != "none":
password = getpass.getpass(prompt="Backup Password: ", stream=None)
try:
tardata = parse_backup_file(data, password=password)
except InvalidBackupPassword:
log.critical("Invalid backup password")
ctx.exit(1)
except AndroidBackupParsingError:
log.critical("Impossible to parse this backup file, please use Android Backup Extractor instead")
ctx.exit(1)
dbytes = io.BytesIO(tardata)
tar = tarfile.open(fileobj=dbytes)
files = []
for member in tar:
files.append(member.name)
elif os.path.isdir(backup_path):
backup_type = "folder"
backup_path = Path(backup_path).absolute().as_posix()
files = []
for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)):
for fname in subfiles:
files.append(os.path.relpath(os.path.join(root, fname), backup_path))
else:
log.critical("Invalid backup path, path should be a folder or an Android Backup (.ab) file")
ctx.exit(1)
if output and not os.path.exists(output):
try:
os.makedirs(output)
@@ -256,14 +300,6 @@ def check_backup(ctx, iocs, output, backup_path, serial):
indicators = Indicators(log=log)
indicators.load_indicators_files(iocs)
if os.path.isfile(backup_path):
log.critical("The path you specified is a not a folder!")
if os.path.basename(backup_path) == "backup.ab":
log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) "
"to extract 'backup.ab' files!")
ctx.exit(1)
for module in BACKUP_MODULES:
m = module(base_folder=backup_path, output_folder=output,
log=logging.getLogger(module.__module__))
@@ -273,6 +309,11 @@ def check_backup(ctx, iocs, output, backup_path, serial):
if serial:
m.serial = serial
if backup_type == "folder":
m.from_folder(backup_path, files)
else:
m.from_ab(backup_path, tar, files)
run_module(m)

View File

@@ -6,6 +6,7 @@
from .chrome_history import ChromeHistory
from .dumpsys_accessibility import DumpsysAccessibility
from .dumpsys_activities import DumpsysActivities
from .dumpsys_appops import DumpsysAppOps
from .dumpsys_battery_daily import DumpsysBatteryDaily
from .dumpsys_battery_history import DumpsysBatteryHistory
from .dumpsys_dbinfo import DumpsysDBInfo
@@ -25,5 +26,5 @@ from .whatsapp import Whatsapp
ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes, Getprop, Settings,
SELinuxStatus, DumpsysBatteryHistory, DumpsysBatteryDaily,
DumpsysReceivers, DumpsysActivities, DumpsysAccessibility,
DumpsysDBInfo, DumpsysFull, Packages, Logcat, RootBinaries,
Files]
DumpsysDBInfo, DumpsysFull, DumpsysAppOps, Packages, Logcat,
RootBinaries, Files]

View File

@@ -3,6 +3,8 @@
# 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 getpass
import logging
import os
import random
@@ -18,6 +20,8 @@ from adb_shell.exceptions import (AdbCommandFailureException, DeviceAuthError,
UsbDeviceNotFoundError, UsbReadFailedError)
from usb1 import USBErrorAccess, USBErrorBusy
from mvt.android.parsers.backup import (InvalidBackupPassword, parse_ab_header,
parse_backup_file)
from mvt.common.module import InsufficientPrivileges, MVTModule
log = logging.getLogger(__name__)
@@ -243,6 +247,32 @@ class AndroidExtraction(MVTModule):
# Disconnect from the device.
self._adb_disconnect()
def _generate_backup(self, package_name):
self.log.warning("Please check phone and accept Android backup prompt. You may need to set a backup password. \a")
# TODO: Base64 encoding as temporary fix to avoid byte-mangling over the shell transport...
backup_output_b64 = self._adb_command("/system/bin/bu backup -nocompress '{}' | base64".format(
package_name))
backup_output = base64.b64decode(backup_output_b64)
header = parse_ab_header(backup_output)
if not header["backup"]:
self.log.error("Extracting SMS via Android backup failed. No valid backup data found.")
return
if header["encryption"] == "none":
return parse_backup_file(backup_output, password=None)
for password_retry in range(0, 3):
backup_password = getpass.getpass(prompt="Backup password: ", stream=None)
try:
decrypted_backup_tar = parse_backup_file(backup_output, backup_password)
return decrypted_backup_tar
except InvalidBackupPassword:
self.log.error("You provided the wrong password! Please try again...")
self.log.warn("All attempts to decrypt backup with password failed!")
def run(self):
"""Run the main procedure."""
raise NotImplementedError

View File

@@ -0,0 +1,66 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 re
from mvt.android.parsers.dumpsys import parse_dumpsys_appops
from .base import AndroidExtraction
log = logging.getLogger(__name__)
class DumpsysAppOps(AndroidExtraction):
"""This module extracts records from App-op Manager."""
slug = "dumpsys_appops"
def __init__(self, file_path=None, base_folder=None, output_folder=None,
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)
def serialize(self, record):
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append({
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to {perm['name']} : {entry['access']}",
})
return records
def check_indicators(self):
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if perm["name"] == "REQUEST_INSTALL_PACKAGES" and perm["access"] == "allow":
self.log.info("Package %s with REQUEST_INSTALL_PACKAGES permission",
result["package_name"])
def run(self):
self._adb_connect()
output = self._adb_command("dumpsys appops")
self._adb_disconnect()
self.results = parse_dumpsys_appops(output)
self.log.info("Extracted a total of %d records from app-ops manager",
len(self.results))

View File

@@ -5,6 +5,7 @@
import logging
import re
from datetime import datetime, timedelta
from mvt.android.parsers import parse_getprop
@@ -31,4 +32,12 @@ class Getprop(AndroidExtraction):
self.results = parse_getprop(output)
# Alert if phone is outdated.
security_patch = self.results.get("ro.build.version.security_patch", "")
if security_patch:
patch_date = datetime.strptime(security_patch, "%Y-%m-%d")
if (datetime.now() - patch_date) > timedelta(days=6*30):
self.log.warning("This phone has not received security updates for more than "
"six months (last update: %s)", security_patch)
self.log.info("Extracted %d Android system properties", len(self.results))

View File

@@ -31,11 +31,6 @@ ANDROID_DANGEROUS_SETTINGS = [
"key": "upload_apk_enable",
"safe_value": "1",
},
{
"description": "enabled installation of non-market apps",
"key": "install_non_market_apps",
"safe_value": "0",
},
{
"description": "disabled confirmation of adb apps installation",
"key": "adb_install_need_confirm",

View File

@@ -3,10 +3,15 @@
# 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 getpass
import logging
import os
import sqlite3
from mvt.android.parsers.backup import (AndroidBackupParsingError,
parse_tar_for_sms)
from mvt.common.module import InsufficientPrivileges
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
from .base import AndroidExtraction
@@ -16,11 +21,11 @@ log = logging.getLogger(__name__)
SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
SMS_BUGLE_QUERY = """
SELECT
ppl.normalized_destination AS number,
ppl.normalized_destination AS address,
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
THEN 2 ELSE 1 END incoming, p.text AS body
FROM messages m, conversations c, parts p,
participants ppl, conversation_participants cp
WHERE (m.conversation_id = c._id)
@@ -32,10 +37,10 @@ WHERE (m.conversation_id = c._id)
SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db"
SMS_MMSMS_QUERY = """
SELECT
address AS number,
address AS address,
date_sent AS timestamp,
type as incoming,
body AS text
body AS body
FROM sms;
"""
@@ -50,12 +55,12 @@ class SMS(AndroidExtraction):
log=log, results=results)
def serialize(self, record):
text = record["text"].replace("\n", "\\n")
body = record["body"].replace("\n", "\\n")
return {
"timestamp": record["isodate"],
"module": self.__class__.__name__,
"event": f"sms_{record['direction']}",
"data": f"{record['number']}: \"{text}\""
"data": f"{record['address']}: \"{body}\""
}
def check_indicators(self):
@@ -63,10 +68,11 @@ class SMS(AndroidExtraction):
return
for message in self.results:
if "text" not in message:
if "body" not in message:
continue
message_links = check_for_links(message["text"])
# FIXME: check links exported from the body previously
message_links = check_for_links(message["body"])
if self.indicators.check_domains(message_links):
self.detected.append(message)
@@ -79,9 +85,9 @@ class SMS(AndroidExtraction):
conn = sqlite3.connect(db_path)
cur = conn.cursor()
if (self.SMS_DB_TYPE == 1):
if self.SMS_DB_TYPE == 1:
cur.execute(SMS_BUGLE_QUERY)
elif (self.SMS_DB_TYPE == 2):
elif self.SMS_DB_TYPE == 2:
cur.execute(SMS_MMSMS_QUERY)
names = [description[0] for description in cur.description]
@@ -96,7 +102,7 @@ class SMS(AndroidExtraction):
# If we find links in the messages or if they are empty we add
# them to the list of results.
if check_for_links(message["text"]) or message["text"].strip() == "":
if check_for_links(message["body"]) or message["body"].strip() == "":
self.results.append(message)
cur.close()
@@ -104,12 +110,35 @@ class SMS(AndroidExtraction):
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
def _extract_sms_adb(self):
"""Use the Android backup command to extract SMS data from the native SMS app
It is crucial to use the under-documented "-nocompress" flag to disable the non-standard Java compression
algorithim. This module only supports an unencrypted ADB backup.
"""
backup_tar = self._generate_backup("com.android.providers.telephony")
if not backup_tar:
return
try:
self.results = parse_tar_for_sms(backup_tar)
except AndroidBackupParsingError:
self.log.info("Impossible to read SMS from the Android Backup, please extract the SMS and try extracting it with Android Backup Extractor")
return
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
def run(self):
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")
try:
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)
return
except InsufficientPrivileges:
pass
self.log.warn("No SMS database found. Trying extraction of SMS data using Android backup feature.")
self._extract_sms_adb()

View File

@@ -0,0 +1,46 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 fnmatch
import os
from mvt.common.module import MVTModule
class BackupExtraction(MVTModule):
"""This class provides a base for all backup extractios modules"""
ab = None
def from_folder(self, backup_path, files):
"""
Get all the files and list them
"""
self.backup_path = backup_path
self.files = files
def from_ab(self, file_path, tar, files):
"""
Extract the files
"""
self.ab = file_path
self.tar = tar
self.files = files
def _get_files_by_pattern(self, pattern):
return fnmatch.filter(self.files, pattern)
def _get_file_content(self, file_path):
if self.ab:
try:
member = self.tar.getmember(file_path)
except KeyError:
return None
handle = self.tar.extractfile(member)
else:
handle = open(os.path.join(self.backup_path, file_path), "rb")
data = handle.read()
handle.close()
return data

View File

@@ -3,21 +3,18 @@
# 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 os
import zlib
from mvt.common.module import MVTModule
from mvt.android.modules.backup.base import BackupExtraction
from mvt.android.parsers.backup import parse_sms_file
from mvt.common.utils import check_for_links
class SMS(MVTModule):
class SMS(BackupExtraction):
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:
@@ -27,38 +24,13 @@ class SMS(MVTModule):
if "body" not in message:
continue
message_links = check_for_links(message["body"])
if self.indicators.check_domains(message_links):
if self.indicators.check_domains(message["links"]):
self.detected.append(message)
def _process_sms_file(self, file_path):
self.log.info("Processing SMS backup file at %s", file_path)
with open(file_path, "rb") as handle:
data = zlib.decompress(handle.read())
json_data = json.loads(data)
for entry in json_data:
message_links = check_for_links(entry["body"])
# If we find links in the messages or if they are empty we add them to the list.
if message_links or entry["body"].strip() == "":
self.results.append(entry)
def run(self):
app_folder = os.path.join(self.base_folder,
"apps",
"com.android.providers.telephony",
"d_f")
if not os.path.exists(app_folder):
raise FileNotFoundError("Unable to find the SMS backup folder")
for file_name in os.listdir(app_folder):
if not file_name.endswith("_sms_backup"):
continue
file_path = os.path.join(app_folder, file_name)
self._process_sms_file(file_path)
for file in self._get_files_by_pattern("apps/com.android.providers.telephony/d_f/*_sms_backup"):
self.log.info("Processing SMS backup file at %s", file)
data = self._get_file_content(file)
self.results.extend(parse_sms_file(data))
self.log.info("Extracted a total of %d SMS messages containing links",
len(self.results))

View File

@@ -5,6 +5,7 @@
from .accessibility import Accessibility
from .activities import Activities
from .appops import Appops
from .battery_daily import BatteryDaily
from .battery_history import BatteryHistory
from .dbinfo import DBInfo
@@ -12,5 +13,5 @@ from .getprop import Getprop
from .packages import Packages
from .receivers import Receivers
BUGREPORT_MODULES = [Accessibility, Activities, BatteryDaily, BatteryHistory,
DBInfo, Getprop, Packages, Receivers]
BUGREPORT_MODULES = [Accessibility, Activities, Appops, BatteryDaily,
BatteryHistory, DBInfo, Getprop, Packages, Receivers]

View File

@@ -0,0 +1,78 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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
from mvt.android.parsers import parse_dumpsys_appops
from .base import BugReportModule
log = logging.getLogger(__name__)
class Appops(BugReportModule):
"""This module extracts information on package from App-Ops Manager."""
def __init__(self, file_path=None, base_folder=None, output_folder=None,
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)
def serialize(self, record):
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append({
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to {perm['name']} : {entry['access']}",
})
return records
def check_indicators(self):
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if perm["name"] == "REQUEST_INSTALL_PACKAGES" and perm["access"] == "allow":
self.log.info("Package %s with REQUEST_INSTALL_PACKAGES permission", result["package_name"])
def run(self):
content = self._get_dumpstate_file()
if not content:
self.log.error("Unable to find dumpstate file. Did you provide a valid bug report archive?")
return
lines = []
in_appops = False
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE appops:":
in_appops = True
continue
if not in_appops:
continue
if line.strip().startswith("------------------------------------------------------------------------------"):
break
lines.append(line)
self.results = parse_dumpsys_appops("\n".join(lines))
self.log.info("Identified a total of %d packages in App-Ops Manager",
len(self.results))

View File

@@ -5,6 +5,7 @@
import logging
import re
from datetime import datetime, timedelta
from mvt.android.parsers import parse_getprop
@@ -47,4 +48,12 @@ class Getprop(BugReportModule):
self.results = parse_getprop("\n".join(lines))
# Alert if phone is outdated.
security_patch = self.results.get("ro.build.version.security_patch", "")
if security_patch:
patch_date = datetime.strptime(security_patch, "%Y-%m-%d")
if (datetime.now() - patch_date) > timedelta(days=6*30):
self.log.warning("This phone has not received security updates for more than "
"six months (last update: %s)", security_patch)
self.log.info("Extracted %d Android system properties", len(self.results))

View File

@@ -6,8 +6,6 @@
import logging
import re
from mvt.android.modules.adb.packages import Packages as PCK
from .base import BugReportModule
log = logging.getLogger(__name__)
@@ -53,7 +51,58 @@ class Packages(BugReportModule):
continue
@staticmethod
def parse_packages_list(output):
def parse_package_for_details(output):
details = {
"uid": "",
"version_name": "",
"version_code": "",
"timestamp": "",
"first_install_time": "",
"last_update_time": "",
"requested_permissions": [],
}
in_install_permissions = False
in_runtime_permissions = False
for line in output.splitlines():
if in_install_permissions:
if line.startswith(" " * 4) and not line.startswith(" " * 6):
in_install_permissions = False
continue
permission = line.strip().split(":")[0]
if permission not in details["requested_permissions"]:
details["requested_permissions"].append(permission)
if in_runtime_permissions:
if not line.startswith(" " * 8):
in_runtime_permissions = False
continue
permission = line.strip().split(":")[0]
if permission not in details["requested_permissions"]:
details["requested_permissions"].append(permission)
if line.strip().startswith("userId="):
details["uid"] = line.split("=")[1].strip()
elif line.strip().startswith("versionName="):
details["version_name"] = line.split("=")[1].strip()
elif line.strip().startswith("versionCode="):
details["version_code"] = line.split("=", 1)[1].strip()
elif line.strip().startswith("timeStamp="):
details["timestamp"] = line.split("=")[1].strip()
elif line.strip().startswith("firstInstallTime="):
details["first_install_time"] = line.split("=")[1].strip()
elif line.strip().startswith("lastUpdateTime="):
details["last_update_time"] = line.split("=")[1].strip()
elif line.strip() == "install permissions:":
in_install_permissions = True
elif line.strip() == "runtime permissions:":
in_runtime_permissions = True
return details
def parse_packages_list(self, output):
pkg_rxp = re.compile(r" Package \[(.+?)\].*")
results = []
@@ -63,9 +112,10 @@ class Packages(BugReportModule):
for line in output.splitlines():
if line.startswith(" Package ["):
if len(lines) > 0:
details = PCK.parse_package_for_details("\n".join(lines))
details = self.parse_package_for_details("\n".join(lines))
package.update(details)
results.append(package)
lines = []
package = {}
matches = pkg_rxp.findall(line)

View File

@@ -5,7 +5,7 @@
from .dumpsys import (parse_dumpsys_accessibility,
parse_dumpsys_activity_resolver_table,
parse_dumpsys_battery_daily,
parse_dumpsys_appops, parse_dumpsys_battery_daily,
parse_dumpsys_battery_history, parse_dumpsys_dbinfo,
parse_dumpsys_receiver_resolver_table)
from .getprop import parse_getprop

View File

@@ -0,0 +1,202 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 json
import tarfile
import zlib
from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
PBKDF2_KEY_SIZE = 32
class AndroidBackupParsingError(Exception):
"""Exception raised file parsing an android backup file"""
class AndroidBackupNotImplemented(AndroidBackupParsingError):
pass
class InvalidBackupPassword(AndroidBackupParsingError):
pass
def to_utf8_bytes(input_bytes):
output = []
for byte in input_bytes:
if byte < ord(b'\x80'):
output.append(byte)
else:
output.append(ord('\xef') | (byte >> 12))
output.append(ord('\xbc') | ((byte >> 6) & ord('\x3f')))
output.append(ord('\x80') | (byte & ord('\x3f')))
return bytes(output)
def parse_ab_header(data):
"""
Parse the header of an Android Backup file
Returns a dict {'backup': True, 'compression': False,
'encryption': "none", 'version': 4}
"""
if data.startswith(b"ANDROID BACKUP"):
[magic_header, version, is_compressed, encryption, tar_data] = data.split(b"\n", 4)
return {
"backup": True,
"compression": (is_compressed == b"1"),
"version": int(version),
"encryption": encryption.decode("utf-8")
}
return {
"backup": False,
"compression": None,
"version": None,
"encryption": None
}
def decrypt_master_key(password, user_salt, user_iv, pbkdf2_rounds, master_key_blob, format_version, checksum_salt):
"""Generate AES key from user password uisng PBKDF2
The backup master key is extracted from the master key blog after decryption.
"""
# Derive key from password using PBKDF2
kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=32, salt=user_salt, iterations=pbkdf2_rounds)
key = kdf.derive(password.encode("utf-8"))
# Decrypt master key blob
cipher = Cipher(algorithms.AES(key), modes.CBC(user_iv))
decryptor = cipher.decryptor()
try:
decryted_master_key_blob = decryptor.update(master_key_blob) + decryptor.finalize()
# Extract key and IV from decrypted blob.
key_blob = io.BytesIO(decryted_master_key_blob)
master_iv_length = ord(key_blob.read(1))
master_iv = key_blob.read(master_iv_length)
master_key_length = ord(key_blob.read(1))
master_key = key_blob.read(master_key_length)
master_key_checksum_length = ord(key_blob.read(1))
master_key_checksum = key_blob.read(master_key_checksum_length)
except TypeError:
raise InvalidBackupPassword()
# Handle quirky encoding of master key bytes in Android original Java crypto code
if format_version > 1:
hmac_mk = to_utf8_bytes(master_key)
else:
hmac_mk = master_key
# Derive checksum to confirm successful backup decryption.
kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=32, salt=checksum_salt, iterations=pbkdf2_rounds)
calculated_checksum = kdf.derive(hmac_mk)
if master_key_checksum != calculated_checksum:
raise InvalidBackupPassword()
return master_key, master_iv
def decrypt_backup_data(encrypted_backup, password, encryption_algo, format_version):
"""
Generate encryption keyffrom password and do decryption
"""
if encryption_algo != b"AES-256":
raise AndroidBackupNotImplemented("Encryption Algorithm not implemented")
if password is None:
raise InvalidBackupPassword()
[user_salt, checksum_salt, pbkdf2_rounds, user_iv, master_key_blob, encrypted_data] = encrypted_backup.split(b"\n", 5)
user_salt = bytes.fromhex(user_salt.decode("utf-8"))
checksum_salt = bytes.fromhex(checksum_salt.decode("utf-8"))
pbkdf2_rounds = int(pbkdf2_rounds)
user_iv = bytes.fromhex(user_iv.decode("utf-8"))
master_key_blob = bytes.fromhex(master_key_blob.decode("utf-8"))
# Derive decryption master key from password
master_key, master_iv = decrypt_master_key(password=password, user_salt=user_salt, user_iv=user_iv,
pbkdf2_rounds=pbkdf2_rounds, master_key_blob=master_key_blob,
format_version=format_version, checksum_salt=checksum_salt)
# Decrypt and unpad backup data using derivied key
cipher = Cipher(algorithms.AES(master_key), modes.CBC(master_iv))
decryptor = cipher.decryptor()
decrypted_tar = decryptor.update(encrypted_data) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
return unpadder.update(decrypted_tar)
def parse_backup_file(data, password=None):
"""
Parse an ab file, returns a tar file
"""
if not data.startswith(b"ANDROID BACKUP"):
raise AndroidBackupParsingError("Invalid file header")
[magic_header, version, is_compressed, encryption_algo, tar_data] = data.split(b"\n", 4)
version = int(version)
is_compressed = int(is_compressed)
if encryption_algo != b"none":
tar_data = decrypt_backup_data(tar_data, password, encryption_algo, format_version=version)
if is_compressed:
try:
tar_data = zlib.decompress(tar_data)
except zlib.error:
raise AndroidBackupParsingError("Impossible to decompress the backup file")
return tar_data
def parse_tar_for_sms(data):
"""
Extract SMS from a tar backup archive
Returns an array of SMS
"""
dbytes = io.BytesIO(data)
tar = tarfile.open(fileobj=dbytes)
try:
member = tar.getmember("apps/com.android.providers.telephony/d_f/000000_sms_backup")
except KeyError:
return []
dhandler = tar.extractfile(member)
return parse_sms_file(dhandler.read())
def parse_sms_file(data):
"""
Parse an SMS file extracted from a folder
Returns a list of SMS entries
"""
res = []
data = zlib.decompress(data)
json_data = json.loads(data)
for entry in json_data:
message_links = check_for_links(entry["body"])
utc_timestamp = datetime.datetime.utcfromtimestamp(int(entry["date"]) / 1000)
entry["isodate"] = convert_timestamp_to_iso(utc_timestamp)
entry["direction"] = ("sent" if int(entry["date_sent"]) else "received")
# If we find links in the messages or if they are empty we add them to the list.
if message_links or entry["body"].strip() == "":
entry["links"] = message_links
res.append(entry)
return res

View File

@@ -4,6 +4,9 @@
# https://license.mvt.re/1.1/
import re
from datetime import datetime
from mvt.common.utils import convert_timestamp_to_iso
def parse_dumpsys_accessibility(output):
@@ -285,3 +288,88 @@ def parse_dumpsys_receiver_resolver_table(output):
})
return results
def parse_dumpsys_appops(output):
results = []
perm = {}
package = {}
entry = {}
uid = None
in_packages = False
for line in output.splitlines():
if line.startswith(" Uid 0:"):
in_packages = True
if not in_packages:
continue
if line.startswith(" Uid "):
uid = line[6:-1]
continue
if line.startswith(" Package "):
if entry:
perm["entries"].append(entry)
entry = {}
if package:
if perm:
package["permissions"].append(perm)
perm = {}
results.append(package)
package = {
"package_name": line[12:-1],
"permissions": [],
"uid": uid,
}
continue
if line.startswith(" ") and line[6] != " ":
if entry:
perm["entries"].append(entry)
entry = {}
if perm:
package["permissions"].append(perm)
perm = {}
perm["name"] = line.split()[0]
perm["entries"] = []
if len(line.split()) > 1:
perm["access"] = line.split()[1][1:-2]
continue
if line.startswith(" "):
# Permission entry like:
# Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms)
if entry:
perm["entries"].append(entry)
entry = {}
entry["access"] = line.split(":")[0].strip()
entry["type"] = line[line.find("[")+1:line.find("]")]
try:
entry["timestamp"] = convert_timestamp_to_iso(
datetime.strptime(
line[line.find("]")+1:line.find("(")].strip(),
"%Y-%m-%d %H:%M:%S.%f"))
except ValueError:
# Invalid date format
pass
if line.strip() == "":
break
if entry:
perm["entries"].append(entry)
if perm:
package["permissions"].append(perm)
if package:
results.append(package)
return results

View File

@@ -6,7 +6,7 @@
import requests
from packaging import version
MVT_VERSION = "1.5.1"
MVT_VERSION = "1.5.4"
def check_for_updates():

View File

@@ -7,6 +7,7 @@ import binascii
import glob
import logging
import os
import os.path
import shutil
import sqlite3
@@ -27,7 +28,7 @@ class DecryptBackup:
:param backup_path: Path to the encrypted backup folder
:param dest_path: Path to the folder where to store the decrypted backup
"""
self.backup_path = backup_path
self.backup_path = os.path.abspath(backup_path)
self.dest_path = dest_path
self._backup = None
self._decryption_key = None

View File

@@ -7,6 +7,7 @@ import os
import plistlib
from mvt.common.module import DatabaseNotFoundError
from mvt.ios.versions import latest_ios_version
from ..base import IOSExtraction
@@ -41,3 +42,9 @@ class BackupInfo(IOSExtraction):
value = info.get(field, None)
self.log.info("%s: %s", field, value)
self.results[field] = value
if "Product Version" in info:
latest = latest_ios_version()
if info["Product Version"] != latest["version"]:
self.log.warning("This phone is running an outdated iOS version: %s (latest is %s)",
info["Product Version"], latest['version'])

View File

@@ -74,12 +74,14 @@ class ConfigurationProfiles(IOSExtraction):
conf_plist = plistlib.load(handle)
except Exception:
conf_plist = {}
if "SignerCerts" in conf_plist:
conf_plist["SignerCerts"] = [b64encode(x) for x in conf_plist["SignerCerts"]]
if "OTAProfileStub" in conf_plist:
if "SignerCerts" in conf_plist["OTAProfileStub"]:
conf_plist["OTAProfileStub"]["SignerCerts"] = [b64encode(x) for x in conf_plist["OTAProfileStub"]["SignerCerts"]]
if "PayloadContent" in conf_plist["OTAProfileStub"]:
if "EnrollmentIdentityPersistentID" in conf_plist["OTAProfileStub"]["PayloadContent"]:
conf_plist["OTAProfileStub"]["PayloadContent"]["EnrollmentIdentityPersistentID"] = b64encode(conf_plist["OTAProfileStub"]["PayloadContent"]["EnrollmentIdentityPersistentID"])
if "PushTokenDataSentToServerKey" in conf_plist:
conf_plist["PushTokenDataSentToServerKey"] = b64encode(conf_plist["PushTokenDataSentToServerKey"])
if "LastPushTokenHash" in conf_plist:
@@ -88,6 +90,8 @@ class ConfigurationProfiles(IOSExtraction):
for x in range(len(conf_plist["PayloadContent"])):
if "PERSISTENT_REF" in conf_plist["PayloadContent"][x]:
conf_plist["PayloadContent"][x]["PERSISTENT_REF"] = b64encode(conf_plist["PayloadContent"][x]["PERSISTENT_REF"])
if "IdentityPersistentRef" in conf_plist["PayloadContent"][x]:
conf_plist["PayloadContent"][x]["IdentityPersistentRef"] = b64encode(conf_plist["PayloadContent"][x]["IdentityPersistentRef"])
self.results.append({
"file_id": conf_file["file_id"],

View File

@@ -64,7 +64,7 @@ class ShutdownLog(IOSExtraction):
mac_timestamp = int(line[line.find("[")+1:line.find("]")])
except ValueError:
try:
start = line.find(" @")+2
start = line.find(" @") + 2
mac_timestamp = int(line[start:start+10])
except Exception:
mac_timestamp = 0

View File

@@ -88,9 +88,8 @@ class SMS(IOSExtraction):
for index, value in enumerate(item):
# We base64 escape some of the attributes that could contain
# binary data.
if (names[index] == "attributedBody" or
names[index] == "payload_data" or
names[index] == "message_summary_info") and value:
if (names[index] == "attributedBody" or names[index] == "payload_data"
or names[index] == "message_summary_info") and value:
value = b64encode(value).decode()
# We store the value of each column under the proper key.

View File

@@ -73,8 +73,8 @@ class SMSAttachments(IOSExtraction):
attachment["service"] = attachment["service"] or "Unknown"
attachment["filename"] = attachment["filename"] or "NULL"
if (attachment["filename"].startswith("/var/tmp/") and attachment["filename"].endswith("-1") and
attachment["direction"] == "received"):
if (attachment["filename"].startswith("/var/tmp/") and attachment["filename"].endswith("-1")
and attachment["direction"] == "received"):
self.log.warn(f"Suspicious iMessage attachment '{attachment['filename']}' on {attachment['isodate']}")
self.detected.append(attachment)

View File

@@ -237,6 +237,9 @@ IPHONE_IOS_VERSIONS = [
{"build": "19C56", "version": "15.2"},
{"build": "19C63", "version": "15.2.1"},
{"build": "19D50", "version": "15.3"},
{"build": "19D52", "version": "15.3.1"},
{"build": "19E241", "version": "15.4"},
{"build": "19E258", "version": "15.4.1"}
]
@@ -251,3 +254,7 @@ def find_version_by_build(build):
for version in IPHONE_IOS_VERSIONS:
if build == version["build"]:
return version["version"]
def latest_ios_version():
return IPHONE_IOS_VERSIONS[-1]

View File

@@ -29,6 +29,7 @@ requires = (
# Android dependencies:
"adb-shell>=0.4.2",
"libusb1>=2.0.1",
"cryptography>=36.0.1"
)

View File

View File

@@ -0,0 +1,78 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 logging
import os
import tarfile
from mvt.android.modules.backup.sms import SMS
from mvt.android.parsers.backup import parse_backup_file
from mvt.common.module import run_module
from ..utils import get_android_backup_folder
class TestBackupModule:
def test_module_folder(self):
backup_path = get_android_backup_folder()
mod = SMS(base_folder=backup_path, log=logging)
files = []
for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)):
for fname in subfiles:
files.append(os.path.relpath(os.path.join(root, fname), backup_path))
mod.from_folder(backup_path, files)
run_module(mod)
assert len(mod.results) == 1
assert len(mod.results[0]["links"]) == 1
assert mod.results[0]["links"][0] == "https://google.com/"
def test_module_file(self):
fpath = os.path.join(get_android_backup_folder(), "backup.ab")
mod = SMS(base_folder=fpath, log=logging)
with open(fpath, "rb") as f:
data = f.read()
tardata = parse_backup_file(data)
dbytes = io.BytesIO(tardata)
tar = tarfile.open(fileobj=dbytes)
files = []
for member in tar:
files.append(member.name)
mod.from_ab(fpath, tar, files)
run_module(mod)
assert len(mod.results) == 1
assert len(mod.results[0]["links"]) == 1
def test_module_file2(self):
fpath = os.path.join(get_android_backup_folder(), "backup2.ab")
mod = SMS(base_folder=fpath, log=logging)
with open(fpath, "rb") as f:
data = f.read()
tardata = parse_backup_file(data, password="123456")
dbytes = io.BytesIO(tardata)
tar = tarfile.open(fileobj=dbytes)
files = []
for member in tar:
files.append(member.name)
mod.from_ab(fpath, tar, files)
run_module(mod)
assert len(mod.results) == 1
assert len(mod.results[0]["links"]) == 1
def test_module_file3(self):
fpath = os.path.join(get_android_backup_folder(), "backup3.ab")
mod = SMS(base_folder=fpath, log=logging)
with open(fpath, "rb") as f:
data = f.read()
tardata = parse_backup_file(data)
dbytes = io.BytesIO(tardata)
tar = tarfile.open(fileobj=dbytes)
files = []
for member in tar:
files.append(member.name)
mod.from_ab(fpath, tar, files)
run_module(mod)
assert len(mod.results) == 1
assert len(mod.results[0]["links"]) == 1

View File

@@ -0,0 +1,58 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 hashlib
import logging
from mvt.android.parsers.backup import parse_backup_file, parse_tar_for_sms
from ..utils import get_artifact
class TestBackupParsing:
def test_parsing_noencryption(self):
file = get_artifact("android_backup/backup.ab")
with open(file, "rb") as f:
data = f.read()
ddata = parse_backup_file(data)
m = hashlib.sha256()
m.update(ddata)
assert m.hexdigest() == "0799b583788908f06bccb854608cede375041ee878722703a39182edeb008324"
sms = parse_tar_for_sms(ddata)
assert isinstance(sms, list)
assert len(sms) == 1
assert len(sms[0]["links"]) == 1
assert sms[0]["links"][0] == "https://google.com/"
def test_parsing_encryption(self):
file = get_artifact("android_backup/backup2.ab")
with open(file, "rb") as f:
data = f.read()
ddata = parse_backup_file(data, password="123456")
m = hashlib.sha256()
m.update(ddata)
assert m.hexdigest() == "f365ace1effbc4902c6aeba241ca61544f8a96ad456c1861808ea87b7dd03896"
sms = parse_tar_for_sms(ddata)
assert isinstance(sms, list)
assert len(sms) == 1
assert len(sms[0]["links"]) == 1
assert sms[0]["links"][0] == "https://google.com/"
def test_parsing_compression(self):
file = get_artifact("android_backup/backup3.ab")
with open(file, "rb") as f:
data = f.read()
ddata = parse_backup_file(data)
m = hashlib.sha256()
m.update(ddata)
assert m.hexdigest() == "33e73df2ede9798dcb3a85c06200ee41c8f52dd2f2e50ffafcceb0407bc13e3a"
sms = parse_tar_for_sms(ddata)
assert isinstance(sms, list)
assert len(sms) == 1
assert len(sms[0]["links"]) == 1
assert sms[0]["links"][0] == "https://google.com/"

View File

@@ -0,0 +1,31 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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
from pathlib import Path
from mvt.android.modules.bugreport.appops import Appops
from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from ..utils import get_artifact_folder
class TestAppopsModule:
def test_appops_parsing(self):
fpath = os.path.join(get_artifact_folder(), "android_data/bugreport/")
m = Appops(base_folder=fpath, log=logging, results=[])
folder_files = []
parent_path = Path(fpath).absolute().as_posix()
for root, subdirs, subfiles in os.walk(os.path.abspath(fpath)):
for file_name in subfiles:
folder_files.append(os.path.relpath(os.path.join(root, file_name), parent_path))
m.from_folder(fpath, folder_files)
run_module(m)
assert len(m.results) == 12
assert len(m.timeline) == 16
assert len(m.detected) == 0

View File

@@ -0,0 +1,29 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 hashlib
import logging
from mvt.android.parsers.dumpsys import parse_dumpsys_appops
from ..utils import get_artifact
class TestDumpsysParsing:
def test_appops_parsing(self):
file = get_artifact("android_data/dumpsys_appops.txt")
with open(file) as f:
data = f.read()
res = parse_dumpsys_appops(data)
assert len(res) == 12
assert res[0]["package_name"] == "com.android.phone"
assert res[0]["uid"] == "0"
assert len(res[0]["permissions"]) == 1
assert res[0]["permissions"][0]["name"] == "MANAGE_IPSEC_TUNNELS"
assert res[0]["permissions"][0]["access"] == "allow"
assert res[6]["package_name"] == "com.sec.factory.camera"
assert len(res[6]["permissions"][1]["entries"]) == 1
assert len(res[11]["permissions"]) == 4

View File

@@ -0,0 +1,2 @@
xœEN]
Â0 ¾Jè³Z«ku;<3B>wº‚[ˆxwS„¼|ÿéßʇ°"‘êÔÑÙæ|2­Ú©G<%9ŒÄ0ÕGÎ01ê´Ž9Ç'Æ<kIÏ(Iã+m—«i­ûÑwÂ…ëŽ`bϯ:º7x+5T<35>+Ž©$1ŠØÿ_ªâCmVŸá Ö

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,126 @@
Currently running services:
AAS
AODManagerService
CodecSolution
CustomFrequencyManagerService
DeviceRootKeyService
DirEncryptService
DisplaySolution
DockObserver
EngineeringModeService
FMPlayer
HcmManagerService
HermesService
HqmManagerService
-------------------------------------------------------------------------------
DUMP OF SERVICE AAS:
--------- 0.002s was the duration of dumpsys AAS, ending at: 2022-03-29 23:14:26
-------------------------------------------------------------------------------
DUMP OF SERVICE appops:
Current AppOps Service state:
Settings:
top_state_settle_time=+30s0ms
fg_service_state_settle_time=+10s0ms
bg_state_settle_time=+1s0ms
Op mode watchers:
Op COARSE_LOCATION:
#0: ModeCallback{b8f1a14 watchinguid=-1 flags=0x1 from uid=1000 pid=4098}
#1: ModeCallback{e9062d4 watchinguid=-1 flags=0x1 from uid=u0a12 pid=13172}
Op READ_CALL_LOG:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op WRITE_CALL_LOG:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op READ_SMS:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op RECEIVE_SMS:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op RECEIVE_MMS:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Uid 0:
state=cch
Package com.android.phone:
MANAGE_IPSEC_TUNNELS (allow):
Package com.sec.epdg:
MANAGE_IPSEC_TUNNELS (deny):
Uid 1000:
state=pers
LEGACY_STORAGE: mode=allow
Package com.samsung.android.provider.filterprovider:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Package com.samsung.android.smartswitchassistant:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Package com.samsung.clipboardsaveservice:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
RUN_IN_BACKGROUND (allow):
Package com.skms.android.agent:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Package com.sec.factory.camera:
RECORD_AUDIO (allow):
RUN_IN_BACKGROUND (allow):
Access: [pers-s] 2022-03-29 18:37:30.315 (-4h50m23s772ms)
Uid u0a103:
state=cch
COARSE_LOCATION: mode=ignore
LEGACY_STORAGE: mode=allow
Package com.facebook.katana:
READ_CONTACTS (allow):
Access: [bg-tpd] 2022-03-07 18:05:34.325 (-22d4h22m19s762ms)
WRITE_SMS (ignore):
Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms)
Reject: [bg-s]2022-03-10 19:35:06.426 (-19d2h52m47s661ms)
Reject: [cch-s]2022-03-29 18:48:02.923 (-4h39m51s164ms)
WAKE_LOCK (allow):
Access: [fg-s] 2021-05-19 22:02:49.186 (-314d1h25m4s901ms)
Access: [bg-s] 2022-03-29 23:03:03.763 (-24m50s324ms) duration=+33ms
Access: [cch-s] 2022-03-07 14:57:11.635 (-22d7h30m42s452ms)
TOAST_WINDOW (allow):
READ_PHONE_STATE (allow):
Access: [fg-s] 2021-05-19 22:02:53.336 (-314d1h25m0s751ms)
Access: [bg-s] 2022-03-24 21:06:52.731 (-5d1h21m1s356ms)
Access: [cch-s] 2022-03-29 18:57:58.524 (-4h29m55s563ms)
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
READ_DEVICE_IDENTIFIERS (deny):
Reject: [fg-s]2021-05-19 22:02:53.434 (-314d1h25m0s653ms)
Reject: [bg-s]2022-03-24 21:06:56.538 (-5d1h20m57s549ms)
Reject: [cch-s]2022-03-29 18:57:58.644 (-4h29m55s443ms)
Uid u0a104:
state=cch
COARSE_LOCATION: mode=ignore
LEGACY_STORAGE: mode=ignore
Package org.mozilla.firefox:
REQUEST_INSTALL_PACKAGES (allow):
Uid u0a105:
state=cch
Package com.android.carrierdefaultapp:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Uid u0a106:
state=cch
LEGACY_STORAGE: mode=allow
Package com.samsung.safetyinformation:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Uid u0a107:
state=cch
LEGACY_STORAGE: mode=allow
Package com.sec.android.app.clockpackage:
WAKE_LOCK (allow):
Access: [bg-s] 2022-03-29 18:38:31.440 (-4h49m22s647ms) duration=+126ms
Access: [cch-s] 2021-06-07 12:47:06.642 (-295d10h40m47s445ms)
TOAST_WINDOW (allow):
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
--------- 0.053s was the duration of dumpsys appops, ending at: 2022-03-29 23:14:27
-------------------------------------------------------------------------------
DUMP OF SERVICE appwidget:
Providers:
[0] provider ProviderId{user:0, app:10107, cmp:ComponentInfo{com.sec.android.app.clockpackage/com.sec.android.app.clockpackage.alarmwidget.ClockAlarmWidgetProvider}}

View File

@@ -0,0 +1 @@
dumpstate.txt

View File

@@ -0,0 +1,100 @@
Current AppOps Service state:
Settings:
top_state_settle_time=+30s0ms
fg_service_state_settle_time=+10s0ms
bg_state_settle_time=+1s0ms
Op mode watchers:
Op COARSE_LOCATION:
#0: ModeCallback{b8f1a14 watchinguid=-1 flags=0x1 from uid=1000 pid=4098}
#1: ModeCallback{e9062d4 watchinguid=-1 flags=0x1 from uid=u0a12 pid=13172}
Op READ_CALL_LOG:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op WRITE_CALL_LOG:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op READ_SMS:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op RECEIVE_SMS:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Op RECEIVE_MMS:
#0: ModeCallback{4b4eb4e watchinguid=-1 flags=0x0 from uid=1000 pid=4098}
Uid 0:
state=cch
Package com.android.phone:
MANAGE_IPSEC_TUNNELS (allow):
Package com.sec.epdg:
MANAGE_IPSEC_TUNNELS (deny):
Uid 1000:
state=pers
LEGACY_STORAGE: mode=allow
Package com.samsung.android.provider.filterprovider:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Package com.samsung.android.smartswitchassistant:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Package com.samsung.clipboardsaveservice:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
RUN_IN_BACKGROUND (allow):
Package com.skms.android.agent:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Package com.sec.factory.camera:
RECORD_AUDIO (allow):
RUN_IN_BACKGROUND (allow):
Access: [pers-s] 2022-03-29 18:37:30.315 (-4h50m23s772ms)
Uid u0a103:
state=cch
COARSE_LOCATION: mode=ignore
LEGACY_STORAGE: mode=allow
Package com.facebook.katana:
READ_CONTACTS (allow):
Access: [bg-tpd] 2022-03-07 18:05:34.325 (-22d4h22m19s762ms)
WRITE_SMS (ignore):
Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms)
Reject: [bg-s]2022-03-10 19:35:06.426 (-19d2h52m47s661ms)
Reject: [cch-s]2022-03-29 18:48:02.923 (-4h39m51s164ms)
WAKE_LOCK (allow):
Access: [fg-s] 2021-05-19 22:02:49.186 (-314d1h25m4s901ms)
Access: [bg-s] 2022-03-29 23:03:03.763 (-24m50s324ms) duration=+33ms
Access: [cch-s] 2022-03-07 14:57:11.635 (-22d7h30m42s452ms)
TOAST_WINDOW (allow):
READ_PHONE_STATE (allow):
Access: [fg-s] 2021-05-19 22:02:53.336 (-314d1h25m0s751ms)
Access: [bg-s] 2022-03-24 21:06:52.731 (-5d1h21m1s356ms)
Access: [cch-s] 2022-03-29 18:57:58.524 (-4h29m55s563ms)
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
READ_DEVICE_IDENTIFIERS (deny):
Reject: [fg-s]2021-05-19 22:02:53.434 (-314d1h25m0s653ms)
Reject: [bg-s]2022-03-24 21:06:56.538 (-5d1h20m57s549ms)
Reject: [cch-s]2022-03-29 18:57:58.644 (-4h29m55s443ms)
Uid u0a104:
state=cch
COARSE_LOCATION: mode=ignore
LEGACY_STORAGE: mode=ignore
Package org.mozilla.firefox:
REQUEST_INSTALL_PACKAGES (allow):
Uid u0a105:
state=cch
Package com.android.carrierdefaultapp:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Uid u0a106:
state=cch
LEGACY_STORAGE: mode=allow
Package com.samsung.safetyinformation:
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):
Uid u0a107:
state=cch
LEGACY_STORAGE: mode=allow
Package com.sec.android.app.clockpackage:
WAKE_LOCK (allow):
Access: [bg-s] 2022-03-29 18:38:31.440 (-4h49m22s647ms) duration=+126ms
Access: [cch-s] 2021-06-07 12:47:06.642 (-295d10h40m47s445ms)
TOAST_WINDOW (allow):
READ_EXTERNAL_STORAGE (allow):
WRITE_EXTERNAL_STORAGE (allow):

View File

@@ -8,12 +8,12 @@ import logging
from mvt.common.module import run_module
from mvt.ios.modules.backup.backup_info import BackupInfo
from ..utils import get_backup_folder
from ..utils import get_ios_backup_folder
class TestBackupInfoModule:
def test_manifest(self):
m = BackupInfo(base_folder=get_backup_folder(), log=logging)
m = BackupInfo(base_folder=get_ios_backup_folder(), log=logging)
run_module(m)
assert m.results["Build Version"] == "18C66"
assert m.results["IMEI"] == "42"

View File

@@ -9,19 +9,19 @@ from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.mixed.net_datausage import Datausage
from ..utils import get_backup_folder
from ..utils import get_ios_backup_folder
class TestDatausageModule:
def test_datausage(self):
m = Datausage(base_folder=get_backup_folder(), log=logging, results=[])
m = Datausage(base_folder=get_ios_backup_folder(), log=logging, results=[])
run_module(m)
assert len(m.results) == 42
assert len(m.timeline) == 60
assert len(m.detected) == 0
def test_detection(self, indicator_file):
m = Datausage(base_folder=get_backup_folder(), log=logging, results=[])
m = Datausage(base_folder=get_ios_backup_folder(), log=logging, results=[])
ind = Indicators(log=logging)
ind.parse_stix2(indicator_file)
# Adds a file that exists in the manifest.

View File

@@ -9,19 +9,19 @@ from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.backup.manifest import Manifest
from ..utils import get_backup_folder
from ..utils import get_ios_backup_folder
class TestManifestModule:
def test_manifest(self):
m = Manifest(base_folder=get_backup_folder(), log=logging, results=[])
m = Manifest(base_folder=get_ios_backup_folder(), log=logging, results=[])
run_module(m)
assert len(m.results) == 3721
assert len(m.timeline) == 5881
assert len(m.detected) == 0
def test_detection(self, indicator_file):
m = Manifest(base_folder=get_backup_folder(), log=logging, results=[])
m = Manifest(base_folder=get_ios_backup_folder(), log=logging, results=[])
ind = Indicators(log=logging)
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["file_names"].append("com.apple.CoreBrightness.plist")

View File

@@ -9,12 +9,12 @@ from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.mixed.safari_browserstate import SafariBrowserState
from ..utils import get_backup_folder
from ..utils import get_ios_backup_folder
class TestSafariBrowserStateModule:
def test_parsing(self):
m = SafariBrowserState(base_folder=get_backup_folder(), log=logging, results=[])
m = SafariBrowserState(base_folder=get_ios_backup_folder(), log=logging, results=[])
m.is_backup = True
run_module(m)
assert m.file_path is not None
@@ -23,7 +23,7 @@ class TestSafariBrowserStateModule:
assert len(m.detected) == 0
def test_detection(self, indicator_file):
m = SafariBrowserState(base_folder=get_backup_folder(), log=logging, results=[])
m = SafariBrowserState(base_folder=get_ios_backup_folder(), log=logging, results=[])
m.is_backup = True
ind = Indicators(log=logging)
ind.parse_stix2(indicator_file)

31
tests/ios/test_sms.py Normal file
View File

@@ -0,0 +1,31 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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
from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.mixed.sms import SMS
from ..utils import get_ios_backup_folder
class TestSMSModule:
def test_sms(self):
m = SMS(base_folder=get_ios_backup_folder(), log=logging, results=[])
run_module(m)
assert len(m.results) == 1
assert len(m.timeline) == 1
assert len(m.detected) == 0
def test_detection(self, indicator_file):
m = SMS(base_folder=get_ios_backup_folder(), log=logging, results=[])
ind = Indicators(log=logging)
ind.parse_stix2(indicator_file)
# Adds a file that exists in the manifest.
ind.ioc_collections[0]["domains"].append("badbadbad.example.org")
m.indicators = ind
run_module(m)
assert len(m.detected) == 1

View File

@@ -9,12 +9,12 @@ from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.mixed.tcc import TCC
from ..utils import get_backup_folder
from ..utils import get_ios_backup_folder
class TestTCCtModule:
def test_tcc(self):
m = TCC(base_folder=get_backup_folder(), log=logging, results=[])
m = TCC(base_folder=get_ios_backup_folder(), log=logging, results=[])
run_module(m)
assert len(m.results) == 11
assert len(m.timeline) == 11
@@ -24,7 +24,7 @@ class TestTCCtModule:
assert m.results[0]["auth_value"] == "allowed"
def test_tcc_detection(self, indicator_file):
m = TCC(base_folder=get_backup_folder(), log=logging, results=[])
m = TCC(base_folder=get_ios_backup_folder(), log=logging, results=[])
ind = Indicators(log=logging)
ind.parse_stix2(indicator_file)
m.indicators = ind

View File

@@ -0,0 +1,18 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 click.testing import CliRunner
from mvt.ios.cli import check_backup
from .utils import get_ios_backup_folder
class TestCheckBackupCommand:
def test_check(self):
runner = CliRunner()
path = get_ios_backup_folder()
result = runner.invoke(check_backup, [path])
assert result.exit_code == 0

View File

@@ -0,0 +1,20 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 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 click.testing import CliRunner
from mvt.android.cli import check_bugreport
from .utils import get_artifact_folder
class TestCheckBugreportCommand:
def test_check(self):
runner = CliRunner()
path = os.path.join(get_artifact_folder(), "android_data/bugreport/")
result = runner.invoke(check_bugreport, [path])
assert result.exit_code == 0

View File

@@ -20,9 +20,13 @@ def get_artifact_folder():
return os.path.join(os.path.dirname(__file__), "artifacts")
def get_backup_folder():
def get_ios_backup_folder():
return os.path.join(os.path.dirname(__file__), "artifacts", "ios_backup")
def get_android_backup_folder():
return os.path.join(os.path.dirname(__file__), "artifacts", "android_backup")
def get_indicator_file():
print("PYTEST env", os.getenv("PYTEST_CURRENT_TEST"))