mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-15 01:52:45 +00:00
Compare commits
51 Commits
v2.2.2
...
feature/po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95b2f04db6 | ||
|
|
289b7efdeb | ||
|
|
166a63e14c | ||
|
|
1b933fdb12 | ||
|
|
0c0ff7012b | ||
|
|
f9b0d07a81 | ||
|
|
d14bcdd05f | ||
|
|
e026bb0a76 | ||
|
|
253b4f031a | ||
|
|
ec14297643 | ||
|
|
3142d86edd | ||
|
|
c18998d771 | ||
|
|
22fd794fb8 | ||
|
|
27c5c76dc2 | ||
|
|
fafbac3545 | ||
|
|
bbfaadd297 | ||
|
|
85abed55b6 | ||
|
|
2fbd7607ef | ||
|
|
3787dc48cd | ||
|
|
f814244ff8 | ||
|
|
11730f164f | ||
|
|
912fb060cb | ||
|
|
a9edf4a9fe | ||
|
|
ea7b9066ba | ||
|
|
fd81e3aa13 | ||
|
|
15477cc187 | ||
|
|
551b95b38b | ||
|
|
d767abb912 | ||
|
|
8a507b0a0b | ||
|
|
63b95ee6a5 | ||
|
|
c8ae495971 | ||
|
|
33d092692e | ||
|
|
b1e5dc715f | ||
|
|
1dc1ee2238 | ||
|
|
a2cbaacfce | ||
|
|
801fe367ac | ||
|
|
0d653be4dd | ||
|
|
179b6976fa | ||
|
|
577fcf752d | ||
|
|
2942209f62 | ||
|
|
06bf7b9cb1 | ||
|
|
b5d7e528de | ||
|
|
70c6f0c153 | ||
|
|
49491800fb | ||
|
|
1ad176788b | ||
|
|
11d58022cf | ||
|
|
cc205bfab0 | ||
|
|
671cd07200 | ||
|
|
7581f81464 | ||
|
|
4ed8ff51ff | ||
|
|
fc4e2a9029 |
26
.github/workflows/flake8.yml
vendored
26
.github/workflows/flake8.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Flake8
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.py'
|
||||
|
||||
jobs:
|
||||
flake8_py3:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
architecture: x64
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Install flake8
|
||||
run: pip install flake8
|
||||
- name: Run flake8
|
||||
uses: suo/flake8-github-action@releases/v1
|
||||
with:
|
||||
checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
3
.github/workflows/python-package.yml
vendored
3
.github/workflows/python-package.yml
vendored
@@ -16,8 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# python-version: [3.7, 3.8, 3.9]
|
||||
python-version: [3.8, 3.9]
|
||||
python-version: ['3.8', '3.9', '3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
21
.github/workflows/ruff.yml
vendored
Normal file
21
.github/workflows/ruff.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Ruff
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
ruff_py3:
|
||||
name: Ruff syntax check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
architecture: x64
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
pip install ruff
|
||||
- name: ruff
|
||||
run: |
|
||||
ruff check .
|
||||
80
.github/workflows/scripts/update-ios-releases.py
vendored
Normal file
80
.github/workflows/scripts/update-ios-releases.py
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Python script to download the Apple RSS feed and parse it.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import urllib.request
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
from packaging import version
|
||||
|
||||
|
||||
def download_apple_rss(feed_url):
|
||||
with urllib.request.urlopen(feed_url) as f:
|
||||
rss_feed = f.read().decode('utf-8')
|
||||
print("Downloaded RSS feed from Apple.")
|
||||
return rss_feed
|
||||
|
||||
|
||||
def parse_latest_ios_versions(rss_feed_text):
|
||||
latest_ios_versions = []
|
||||
|
||||
parsed_feed = parseString(rss_feed_text)
|
||||
for item in parsed_feed.getElementsByTagName("item"):
|
||||
title = item.getElementsByTagName("title")[0].firstChild.data
|
||||
if not title.startswith("iOS"):
|
||||
continue
|
||||
|
||||
import re
|
||||
build_match = re.match(r"iOS (?P<version>[\d\.]+) (?P<beta>beta )?(\S*)?\((?P<build>.*)\)", title)
|
||||
if not build_match:
|
||||
print("Could not parse iOS build:", title)
|
||||
continue
|
||||
|
||||
release_info = build_match.groupdict()
|
||||
if release_info["beta"]:
|
||||
print("Skipping beta release:", title)
|
||||
continue
|
||||
|
||||
release_info.pop("beta")
|
||||
latest_ios_versions.append(release_info)
|
||||
|
||||
return latest_ios_versions
|
||||
|
||||
|
||||
def update_mvt(mvt_checkout_path, latest_ios_versions):
|
||||
version_path = os.path.join(mvt_checkout_path, "mvt/ios/data/ios_versions.json")
|
||||
with open(version_path, "r") as version_file:
|
||||
current_versions = json.load(version_file)
|
||||
|
||||
new_entry_count = 0
|
||||
for new_version in latest_ios_versions:
|
||||
for current_version in current_versions:
|
||||
if new_version["build"] == current_version["build"]:
|
||||
break
|
||||
else:
|
||||
# New version that does not exist in current data
|
||||
current_versions.append(new_version)
|
||||
new_entry_count += 1
|
||||
|
||||
if not new_entry_count:
|
||||
print("No new iOS versions found.")
|
||||
else:
|
||||
print("Found {} new iOS versions.".format(new_entry_count))
|
||||
new_version_list = sorted(current_versions, key=lambda x: version.Version(x["version"]))
|
||||
with open(version_path, "w") as version_file:
|
||||
json.dump(new_version_list, version_file, indent=4)
|
||||
|
||||
|
||||
def main():
|
||||
print("Downloading RSS feed...")
|
||||
mvt_checkout_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
|
||||
|
||||
rss_feed = download_apple_rss("https://developer.apple.com/news/releases/rss/releases.rss")
|
||||
latest_ios_version = parse_latest_ios_versions(rss_feed)
|
||||
update_mvt(mvt_checkout_path, latest_ios_version)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
.github/workflows/update-ios-data.yml
vendored
Normal file
29
.github/workflows/update-ios-data.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Update iOS releases and version numbers
|
||||
run-name: ${{ github.actor }} is finding the latest iOS release version and build numbers
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '0 */6 * * *'
|
||||
|
||||
|
||||
jobs:
|
||||
update-ios-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
- name: Run script to fetch latest iOS releases from Apple RSS feed.
|
||||
run: python3 .github/workflows/scripts/update-ios-releases.py
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
title: '[auto] Update iOS releases and versions'
|
||||
commit-message: Add new iOS versions and build numbers
|
||||
branch: auto/add-new-ios-releases
|
||||
body: |
|
||||
This is an automated pull request to update the iOS releases and version numbers.
|
||||
add-paths: |
|
||||
*.json
|
||||
labels: |
|
||||
automated pr
|
||||
5
Makefile
5
Makefile
@@ -1,5 +1,10 @@
|
||||
PWD = $(shell pwd)
|
||||
|
||||
check:
|
||||
flake8
|
||||
pytest -q
|
||||
ruff check -q .
|
||||
|
||||
clean:
|
||||
rm -rf $(PWD)/build $(PWD)/dist $(PWD)/mvt.egg-info
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Some recent phones will enforce the utilisation of a password to encrypt the bac
|
||||
|
||||
## 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:
|
||||
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 from the backup directly with MVT:
|
||||
|
||||
```bash
|
||||
$ mvt-android check-backup --output /path/to/results/ /path/to/backup.ab
|
||||
@@ -32,7 +32,7 @@ $ mvt-android check-backup --output /path/to/results/ /path/to/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
|
||||
INFO [mvt.android.modules.backup.sms] Extracted a total of 64 SMS messages
|
||||
```
|
||||
|
||||
If the backup is encrypted, MVT will prompt you to enter the password.
|
||||
@@ -52,4 +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.
|
||||
|
||||
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).
|
||||
You can then extract SMSs 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).
|
||||
|
||||
@@ -54,7 +54,7 @@ Then you can install MVT directly from [pypi](https://pypi.org/project/mvt/)
|
||||
pip3 install mvt
|
||||
```
|
||||
|
||||
Or from the source code:
|
||||
If you want to have the latest features in development, you can install MVT directly from the source code. If you installed MVT previously from pypi, you should first uninstall it using `pip3 uninstall mvt` and then install from the source code:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mvt-project/mvt.git
|
||||
|
||||
@@ -39,7 +39,9 @@ export MVT_STIX2="/home/user/IOC1.stix2:/home/user/IOC2.stix2"
|
||||
- The [Amnesty International investigations repository](https://github.com/AmnestyTech/investigations) contains STIX-formatted IOCs for:
|
||||
- [Pegasus](https://en.wikipedia.org/wiki/Pegasus_(spyware)) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-07-18_nso/pegasus.stix2))
|
||||
- [Predator from Cytrox](https://citizenlab.ca/2021/12/pegasus-vs-predator-dissidents-doubly-infected-iphone-reveals-cytrox-mercenary-spyware/) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-12-16_cytrox/cytrox.stix2))
|
||||
- [An Android Spyware Campaign Linked to a Mercenary Company](https://github.com/AmnestyTech/investigations/tree/master/2023-03-29_android_campaign) ([STIX2](https://github.com/AmnestyTech/investigations/blob/master/2023-03-29_android_campaign/malware.stix2))
|
||||
- [This repository](https://github.com/Te-k/stalkerware-indicators) contains IOCs for Android stalkerware including [a STIX MVT-compatible file](https://raw.githubusercontent.com/Te-k/stalkerware-indicators/master/generated/stalkerware.stix2).
|
||||
- We are also maintaining [a list of IOCs](https://github.com/mvt-project/mvt-indicators) in STIX format from public spyware campaigns.
|
||||
|
||||
You can automaticallly download the latest public indicator files with the command `mvt-ios download-iocs` or `mvt-android download-iocs`. These commands download the list of indicators listed [here](https://github.com/mvt-project/mvt/blob/main/public_indicators.json) and store them in the [appdir](https://pypi.org/project/appdirs/) folder. They are then loaded automatically by MVT.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ In this page you can find a (reasonably) up-to-date breakdown of the files creat
|
||||
### `analytics.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (if encrypted): :material-close:
|
||||
Backup (if encrypted): :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Analytics` module. The module extracts records from the plists inside the SQLite databases located at *private/var/Keychains/Analytics/\*.db*, which contain various analytics information regarding networking, certificate-pinning, TLS, etc. failures.
|
||||
@@ -16,10 +16,22 @@ If indicators are provided through the command-line, processes and domains are c
|
||||
|
||||
---
|
||||
|
||||
### `applications.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Applications` module. The module extracts the list of applications installed on the device from the `Info.plist` file in backup, or from the `iTunesMetadata.plist` files in a file system dump. These records contains detailed information on the source and installation of the app.
|
||||
|
||||
If indicators are provided through the command-line, processes and application ids are checked against the app name of each application. It also flags any applications not installed from the AppStore. Any matches are stored in *applications_detected.json*.
|
||||
|
||||
---
|
||||
|
||||
### `backup_info.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `BackupInfo` module. The module extracts some details about the backup and the device, such as name, phone number, IMEI, product type and version.
|
||||
@@ -29,7 +41,7 @@ This JSON file is created by mvt-ios' `BackupInfo` module. The module extracts s
|
||||
### `cache_files.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `CacheFiles` module. The module extracts records from all SQLite database files stored on disk with the name *Cache.db*. These databases typically contain data from iOS' [internal URL caching](https://developer.apple.com/documentation/foundation/nsurlcache). Through this module you might be able to recover records of HTTP requests and responses performed my applications as well as system services, that would otherwise be unavailable. For example, you might see HTTP requests part of an exploitation chain performed by an iOS service attempting to download a first stage malicious payload.
|
||||
@@ -38,10 +50,22 @@ If indicators are provided through the command-line, they are checked against th
|
||||
|
||||
---
|
||||
|
||||
### `calendar.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Calendar` module. This module extracts all CalendarItems from the `Calendar.sqlitedb` database. This database contains all calendar entries from the different calendars installed on the phone.
|
||||
|
||||
If indicators are provided through the command-line, email addresses are checked against the inviter's email of the different events. Any matches are stored in *calendar_detected.json*.
|
||||
|
||||
---
|
||||
|
||||
### `calls.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (if encrypted): :material-check:
|
||||
Backup (if encrypted): :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Calls` module. The module extracts records from a SQLite database located at */private/var/mobile/Library/CallHistoryDB/CallHistory.storedata*, which contains records of incoming and outgoing calls, including from messaging apps such as WhatsApp or Skype.
|
||||
@@ -51,7 +75,7 @@ This JSON file is created by mvt-ios' `Calls` module. The module extracts record
|
||||
### `chrome_favicon.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `ChromeFavicon` module. The module extracts records from a SQLite database located at */private/var/mobile/Containers/Data/Application/\*/Library/Application Support/Google/Chrome/Default/Favicons*, which contains a mapping of favicons' URLs and the visited URLs which loaded them.
|
||||
@@ -63,7 +87,7 @@ If indicators are provided through the command-line, they are checked against bo
|
||||
### `chrome_history.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `ChromeHistory` module. The module extracts records from a SQLite database located at */private/var/mobile/Containers/Data/Application/\*/Library/Application Support/Google/Chrome/Default/History*, which contains a history of URL visits.
|
||||
@@ -75,7 +99,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `configuration_profiles.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `ConfigurationProfiles` module. The module extracts details about iOS configuration profiles that have been installed on the device. These should include both default iOS as well as third-party profiles.
|
||||
@@ -87,7 +111,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `contacts.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Contacts` module. The module extracts records from a SQLite database located at */private/var/mobile/Library/AddressBook/AddressBook.sqlitedb*, which contains records from the phone's address book. While this database obviously would not contain any malicious indicators per se, you might want to use it to compare records from other apps (such as iMessage, SMS, etc.) to filter those originating from unknown origins.
|
||||
@@ -97,7 +121,7 @@ This JSON file is created by mvt-ios' `Contacts` module. The module extracts rec
|
||||
### `firefox_favicon.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `FirefoxFavicon` module. The module extracts records from a SQLite database located at */private/var/mobile/profile.profile/browser.db*, which contains a mapping of favicons' URLs and the visited URLs which loaded them.
|
||||
@@ -109,7 +133,7 @@ If indicators are provided through the command-line, they are checked against bo
|
||||
### `firefox_history.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `FirefoxHistory` module. The module extracts records from a SQLite database located at */private/var/mobile/profile.profile/browser.db*, which contains a history of URL visits.
|
||||
@@ -121,7 +145,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `id_status_cache.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (before iOS 14.7): :material-check:
|
||||
Backup (before iOS 14.7): :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `IDStatusCache` module. The module extracts records from a plist file located at */private/var/mobile/Library/Preferences/com.apple.identityservices.idstatuscache.plist*, which contains a cache of Apple user ID authentication. This chance will indicate when apps like Facetime and iMessage first established contacts with other registered Apple IDs. This is significant because it might contain traces of malicious accounts involved in exploitation of those apps.
|
||||
@@ -133,7 +157,7 @@ Starting from iOS 14.7.0, this file is empty or absent.
|
||||
### `shortcuts.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Shortcuts` module. The module extracts records from an SQLite database located at */private/var/mobile/Library/Shortcuts/Shortcuts.sqlite*, which contains records about the Shortcuts application. Shortcuts are a built-in iOS feature which allows users to automation certain actions on their device. In some cases the legitimate Shortcuts app may be abused by spyware to maintain persistence on an infected devices.
|
||||
@@ -143,7 +167,7 @@ This JSON file is created by mvt-ios' `Shortcuts` module. The module extracts re
|
||||
### `interaction_c.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (if encrypted): :material-check:
|
||||
Backup (if encrypted): :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `InteractionC` module. The module extracts records from a SQLite database located at */private/var/mobile/Library/CoreDuet/People/interactionC.db*, which contains details about user interactions with installed apps.
|
||||
@@ -153,7 +177,7 @@ This JSON file is created by mvt-ios' `InteractionC` module. The module extracts
|
||||
### `locationd_clients.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `LocationdClients` module. The module extracts records from a plist file located at */private/var/mobile/Library/Caches/locationd/clients.plist*, which contains a cache of apps which requested access to location services.
|
||||
@@ -163,7 +187,7 @@ This JSON file is created by mvt-ios' `LocationdClients` module. The module extr
|
||||
### `manifest.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `Manifest` module. The module extracts records from the SQLite database *Manifest.db* contained in iTunes backups, and which indexes the locally backed-up files to the original paths on the iOS device.
|
||||
@@ -175,7 +199,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `os_analytics_ad_daily.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `OSAnalyticsADDaily` module. The module extracts records from a plist located *private/var/mobile/Library/Preferences/com.apple.osanalytics.addaily.plist*, 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.
|
||||
@@ -187,7 +211,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `datausage.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
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 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.
|
||||
@@ -199,7 +223,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `netusage.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `Netusage` module. The module extracts records from a SQLite database located */private/var/networkd/netusage.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.
|
||||
@@ -211,7 +235,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `profile_events.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `ProfileEvents` module. The module extracts a timeline of configuration profile operations. For example, it should indicate when a new profile was installed from the Settings app, or when one was removed.
|
||||
@@ -221,7 +245,7 @@ This JSON file is created by mvt-ios' `ProfileEvents` module. The module extract
|
||||
### `safari_browser_state.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (if encrypted): :material-check:
|
||||
Backup (if encrypted): :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `SafariBrowserState` module. The module extracts records from the SQLite databases located at */private/var/mobile/Library/Safari/BrowserState.db* or */private/var/mobile/Containers/Data/Application/\*/Library/Safari/BrowserState.db*, which contain records of opened tabs.
|
||||
@@ -233,7 +257,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `safari_favicon.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `SafariFavicon` module. The module extracts records from the SQLite databases located at */private/var/mobile/Library/Image Cache/Favicons/Favicons.db* or */private/var/mobile/Containers/Data/Application/\*/Library/Image Cache/Favicons/Favicons.db*, which contain mappings of favicons' URLs and the visited URLs which loaded them.
|
||||
@@ -245,7 +269,7 @@ If indicators are provided through the command-line, they are checked against bo
|
||||
### `safari_history.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (if encrypted): :material-check:
|
||||
Backup (if encrypted): :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `SafariHistory` module. The module extracts records from the SQLite databases located at */private/var/mobile/Library/Safari/History.db* or */private/var/mobile/Containers/Data/Application/\*/Library/Safari/History.db*, which contain a history of URL visits.
|
||||
@@ -257,7 +281,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `shutdown_log.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup (if encrypted): :material-close:
|
||||
Backup (if encrypted): :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `ShutdownLog` module. The module extracts records from the shutdown log located at *private/var/db/diagnostics/shutdown.log*. When shutting down an iPhone, a SIGTERM will be sent to all processes runnning. The `shutdown.log` file will log any process (with its pid and path) that did not shut down after the SIGTERM was sent.
|
||||
@@ -269,10 +293,10 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `sms.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `SMS` module. The module extracts a list of SMS messages containing HTTP links from the SQLite database located at */private/var/mobile/Library/SMS/sms.db*.
|
||||
This JSON file is created by mvt-ios' `SMS` module. The module extracts a list of SMS messages from the SQLite database located at */private/var/mobile/Library/SMS/sms.db*.
|
||||
|
||||
If indicators are provided through the command-line, they are checked against the extracted HTTP links. Any matches are stored in *sms_detected.json*.
|
||||
|
||||
@@ -281,7 +305,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `sms_attachments.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `SMSAttachments` module. The module extracts details about attachments sent via SMS or iMessage from the same database used by the `SMS` module. These records might be useful to indicate unique patterns that might be indicative of exploitation attempts leveraging potential vulnerabilities in file format parsers or other forms of file handling by the Messages app.
|
||||
@@ -291,7 +315,7 @@ This JSON file is created by mvt-ios' `SMSAttachments` module. The module extrac
|
||||
### `tcc.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `TCC` module. The module extracts records from a SQLite database located at */private/var/mobile/Library/TCC/TCC.db*, which contains a list of which services such as microphone, camera, or location, apps have been granted or denied access to.
|
||||
@@ -301,7 +325,7 @@ This JSON file is created by mvt-ios' `TCC` module. The module extracts records
|
||||
### `version_history.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `IOSVersionHistory` module. The module extracts records of iOS software updates from analytics plist files located at */private/var/db/analyticsd/Analytics-Journal-\*.ips*.
|
||||
@@ -311,7 +335,7 @@ This JSON file is created by mvt-ios' `IOSVersionHistory` module. The module ext
|
||||
### `webkit_indexeddb.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `WebkitIndexedDB` 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/IndexedDB*, which contains IndexedDB files created by any app installed on the device.
|
||||
@@ -323,7 +347,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `webkit_local_storage.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
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.
|
||||
@@ -335,7 +359,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `webkit_resource_load_statistics.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios `WebkitResourceLoadStatistics` module. The module extracts records from available WebKit ResourceLoadStatistics *observations.db* SQLite3 databases. These records should indicate domain names contacted by apps, including a timestamp.
|
||||
@@ -347,7 +371,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `webkit_safari_view_service.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-close:
|
||||
Backup: :material-close:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `WebkitSafariViewService` module. The module extracts a list of file and folder names located at the following path */private/var/mobile/Containers/Data/Application/\*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/*, which contains files cached by SafariVewService.
|
||||
@@ -359,7 +383,7 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `webkit_session_resource_log.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `WebkitSessionResourceLog` module. The module extracts records from plist files with the name *full_browsing_session_resourceLog.plist*, which contain records of resources loaded by different domains visited.
|
||||
@@ -371,10 +395,10 @@ If indicators are provided through the command-line, they are checked against th
|
||||
### `whatsapp.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios' `WhatsApp` module. The module extracts a list of WhatsApp messages containing HTTP links from the SQLite database located at *private/var/mobile/Containers/Shared/AppGroup/\*/ChatStorage.sqlite*.
|
||||
This JSON file is created by mvt-ios' `WhatsApp` module. The module extracts a list of WhatsApp messages from the SQLite database located at *private/var/mobile/Containers/Shared/AppGroup/\*/ChatStorage.sqlite*.
|
||||
|
||||
If indicators are provided through the command-line, they are checked against the extracted HTTP links. Any matches are stored in *whatsapp_detected.json*.
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
import logging
|
||||
|
||||
import click
|
||||
from rich.logging import RichHandler
|
||||
|
||||
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
||||
from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_IOC,
|
||||
from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_HASHES, HELP_MSG_IOC,
|
||||
HELP_MSG_LIST_MODULES, HELP_MSG_MODULE,
|
||||
HELP_MSG_OUTPUT, HELP_MSG_SERIAL,
|
||||
HELP_MSG_HASHES)
|
||||
HELP_MSG_VERBOSE)
|
||||
from mvt.common.logo import logo
|
||||
from mvt.common.updates import IndicatorsUpdates
|
||||
from mvt.common.utils import init_logging, set_verbose_logging
|
||||
|
||||
from .cmd_check_adb import CmdAndroidCheckADB
|
||||
from .cmd_check_androidqf import CmdAndroidCheckAndroidQF
|
||||
@@ -26,11 +26,8 @@ from .modules.adb.packages import Packages
|
||||
from .modules.backup import BACKUP_MODULES
|
||||
from .modules.bugreport import BUGREPORT_MODULES
|
||||
|
||||
# Setup logging using Rich.
|
||||
LOG_FORMAT = "[%(name)s] %(message)s"
|
||||
logging.basicConfig(level="INFO", format=LOG_FORMAT, handlers=[
|
||||
RichHandler(show_path=False, log_time_format="%X")])
|
||||
log = logging.getLogger(__name__)
|
||||
init_logging()
|
||||
log = logging.getLogger("mvt")
|
||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||
|
||||
|
||||
@@ -64,8 +61,10 @@ def version():
|
||||
@click.option("--from-file", "-f", type=click.Path(exists=True),
|
||||
help="Instead of acquiring from phone, load an existing packages.json file for "
|
||||
"lookups (mainly for debug purposes)")
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.pass_context
|
||||
def download_apks(ctx, all_apks, virustotal, output, from_file, serial):
|
||||
def download_apks(ctx, all_apks, virustotal, output, from_file, serial, verbose):
|
||||
set_verbose_logging(verbose)
|
||||
try:
|
||||
if from_file:
|
||||
download = DownloadAPKs.from_json(from_file)
|
||||
@@ -113,8 +112,10 @@ def download_apks(ctx, all_apks, virustotal, output, from_file, serial):
|
||||
@click.option("--fast", "-f", is_flag=True, help=HELP_MSG_FAST)
|
||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.pass_context
|
||||
def check_adb(ctx, serial, iocs, output, fast, list_modules, module):
|
||||
def check_adb(ctx, serial, iocs, output, fast, list_modules, module, verbose):
|
||||
set_verbose_logging(verbose)
|
||||
cmd = CmdAndroidCheckADB(results_path=output, ioc_files=iocs,
|
||||
module_name=module, serial=serial, fast_mode=fast)
|
||||
|
||||
@@ -142,9 +143,11 @@ def check_adb(ctx, serial, iocs, output, fast, list_modules, module):
|
||||
help=HELP_MSG_OUTPUT)
|
||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.argument("BUGREPORT_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_bugreport(ctx, iocs, output, list_modules, module, bugreport_path):
|
||||
def check_bugreport(ctx, iocs, output, list_modules, module, verbose, bugreport_path):
|
||||
set_verbose_logging(verbose)
|
||||
# Always generate hashes as bug reports are small.
|
||||
cmd = CmdAndroidCheckBugreport(target_path=bugreport_path,
|
||||
results_path=output, ioc_files=iocs,
|
||||
@@ -173,9 +176,11 @@ def check_bugreport(ctx, iocs, output, list_modules, module, bugreport_path):
|
||||
@click.option("--output", "-o", type=click.Path(exists=False),
|
||||
help=HELP_MSG_OUTPUT)
|
||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_backup(ctx, iocs, output, list_modules, backup_path):
|
||||
def check_backup(ctx, iocs, output, list_modules, verbose, backup_path):
|
||||
set_verbose_logging(verbose)
|
||||
# Always generate hashes as backups are generally small.
|
||||
cmd = CmdAndroidCheckBackup(target_path=backup_path, results_path=output,
|
||||
ioc_files=iocs, hashes=True)
|
||||
@@ -205,9 +210,11 @@ def check_backup(ctx, iocs, output, list_modules, backup_path):
|
||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.argument("ANDROIDQF_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_androidqf(ctx, iocs, output, list_modules, module, hashes, androidqf_path):
|
||||
def check_androidqf(ctx, iocs, output, list_modules, module, hashes, verbose, androidqf_path):
|
||||
set_verbose_logging(verbose)
|
||||
cmd = CmdAndroidCheckAndroidQF(target_path=androidqf_path,
|
||||
results_path=output, ioc_files=iocs,
|
||||
module_name=module, hashes=hashes)
|
||||
|
||||
@@ -9,15 +9,15 @@ import os
|
||||
import sys
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, List
|
||||
from typing import List, Optional
|
||||
|
||||
from rich.prompt import Prompt
|
||||
|
||||
from mvt.android.modules.backup.base import BackupExtraction
|
||||
from mvt.android.parsers.backup import (AndroidBackupParsingError,
|
||||
InvalidBackupPassword, parse_ab_header,
|
||||
parse_backup_file)
|
||||
from mvt.common.command import Command
|
||||
from mvt.android.modules.backup.base import BackupExtraction
|
||||
|
||||
from .modules.backup import BACKUP_MODULES
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, List
|
||||
from typing import List, Optional
|
||||
from zipfile import ZipFile
|
||||
|
||||
from mvt.common.command import Command
|
||||
from mvt.android.modules.bugreport.base import BugReportModule
|
||||
from mvt.common.command import Command
|
||||
|
||||
from .modules.bugreport import BUGREPORT_MODULES
|
||||
|
||||
|
||||
@@ -21,15 +21,13 @@ log = logging.getLogger(__name__)
|
||||
class DownloadAPKs(AndroidExtraction):
|
||||
"""DownloadAPKs is the main class operating the download of APKs
|
||||
from the device.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
results_path: Optional[str] = None,
|
||||
all_apks: Optional[bool] = False,
|
||||
packages: Optional[list] = None
|
||||
packages: Optional[list] = None,
|
||||
) -> None:
|
||||
"""Initialize module.
|
||||
:param results_path: Path to the folder where data should be stored
|
||||
|
||||
@@ -31,6 +31,7 @@ class ChromeHistory(AndroidExtraction):
|
||||
super().__init__(file_path=file_path, target_path=target_path,
|
||||
results_path=results_path, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
self.results = []
|
||||
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
return {
|
||||
@@ -55,6 +56,7 @@ class ChromeHistory(AndroidExtraction):
|
||||
:param db_path: Path to the History database to process.
|
||||
|
||||
"""
|
||||
assert isinstance(self.results, list) # assert results type for mypy
|
||||
conn = sqlite3.connect(db_path)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
|
||||
@@ -39,7 +39,7 @@ class Files(AndroidExtraction):
|
||||
log=log, results=results)
|
||||
self.full_find = False
|
||||
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
def serialize(self, record: dict) -> Union[dict, list, None]:
|
||||
if "modified_time" in record:
|
||||
return {
|
||||
"timestamp": record["modified_time"],
|
||||
@@ -62,6 +62,9 @@ class Files(AndroidExtraction):
|
||||
self.detected.append(result)
|
||||
|
||||
def backup_file(self, file_path: str) -> None:
|
||||
if not self.results_path:
|
||||
return
|
||||
|
||||
local_file_name = file_path.replace("/", "_").replace(" ", "-")
|
||||
local_files_folder = os.path.join(self.results_path, "files")
|
||||
if not os.path.exists(local_files_folder):
|
||||
@@ -79,6 +82,7 @@ class Files(AndroidExtraction):
|
||||
file_path, local_file_path)
|
||||
|
||||
def find_files(self, folder: str) -> None:
|
||||
assert isinstance(self.results, list)
|
||||
if self.full_find:
|
||||
cmd = f"find '{folder}' -type f -printf '%T@ %m %s %u %g %p\n' 2> /dev/null"
|
||||
output = self._adb_command(cmd)
|
||||
@@ -121,8 +125,7 @@ class Files(AndroidExtraction):
|
||||
for entry in self.results:
|
||||
self.log.info("Found file in tmp folder at path %s",
|
||||
entry.get("path"))
|
||||
if self.results_path:
|
||||
self.backup_file(entry.get("path"))
|
||||
self.backup_file(entry.get("path"))
|
||||
|
||||
for media_folder in ANDROID_MEDIA_FOLDERS:
|
||||
self.find_files(media_folder)
|
||||
|
||||
@@ -30,6 +30,16 @@ class Getprop(AndroidExtraction):
|
||||
|
||||
self.results = {} if not results else results
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
ioc = self.indicators.check_android_property_name(result.get("name", ""))
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self) -> None:
|
||||
self._adb_connect()
|
||||
output = self._adb_command("getprop")
|
||||
@@ -38,13 +48,14 @@ 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")
|
||||
for entry in self.results:
|
||||
if entry.get("name", "") != "ro.build.version.security_patch":
|
||||
continue
|
||||
patch_date = datetime.strptime(entry["value"], "%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)
|
||||
entry["value"])
|
||||
|
||||
self.log.info("Extracted %d Android system properties",
|
||||
len(self.results))
|
||||
|
||||
@@ -30,9 +30,9 @@ class Logcat(AndroidExtraction):
|
||||
self._adb_connect()
|
||||
|
||||
# Get the current logcat.
|
||||
output = self._adb_command("logcat -d")
|
||||
output = self._adb_command("logcat -d -b all \"*:V\"")
|
||||
# Get the locat prior to last reboot.
|
||||
last_output = self._adb_command("logcat -L")
|
||||
last_output = self._adb_command("logcat -L -b all \"*:V\"")
|
||||
|
||||
if self.results_path:
|
||||
logcat_path = os.path.join(self.results_path,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
from typing import Optional, Union, List
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from rich.console import Console
|
||||
from rich.progress import track
|
||||
|
||||
@@ -43,7 +43,7 @@ FROM sms;
|
||||
|
||||
|
||||
class SMS(AndroidExtraction):
|
||||
"""This module extracts all SMS messages containing links."""
|
||||
"""This module extracts all SMS messages."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -77,8 +77,10 @@ class SMS(AndroidExtraction):
|
||||
if "body" not in message:
|
||||
continue
|
||||
|
||||
# TODO: check links exported from the body previously.
|
||||
message_links = check_for_links(message["body"])
|
||||
message_links = message.get("links", [])
|
||||
if message_links == []:
|
||||
message_links = check_for_links(message["body"])
|
||||
|
||||
if self.indicators.check_domains(message_links):
|
||||
self.detected.append(message)
|
||||
|
||||
@@ -106,15 +108,16 @@ class SMS(AndroidExtraction):
|
||||
message["direction"] = ("received" if message["incoming"] == 1 else "sent")
|
||||
message["isodate"] = convert_unix_to_iso(message["timestamp"])
|
||||
|
||||
# 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["body"]) or message["body"].strip() == "":
|
||||
self.results.append(message)
|
||||
# Extract links in the message body
|
||||
links = check_for_links(message["body"])
|
||||
message["links"] = links
|
||||
|
||||
self.results.append(message)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d SMS messages containing links",
|
||||
self.log.info("Extracted a total of %d SMS messages",
|
||||
len(self.results))
|
||||
|
||||
def _extract_sms_adb(self) -> None:
|
||||
@@ -137,7 +140,7 @@ class SMS(AndroidExtraction):
|
||||
"Android Backup Extractor")
|
||||
return
|
||||
|
||||
self.log.info("Extracted a total of %d SMS messages containing links",
|
||||
self.log.info("Extracted a total of %d SMS messages",
|
||||
len(self.results))
|
||||
|
||||
def run(self) -> None:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
from typing import Union, List, Dict, Any, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from mvt.common.module import MVTModule
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union, List, Any, Dict
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from mvt.android.modules.adb.packages import (DANGEROUS_PERMISSIONS,
|
||||
DANGEROUS_PERMISSIONS_THRESHOLD,
|
||||
ROOT_PACKAGES)
|
||||
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
|
||||
from mvt.common.utils import convert_datetime_to_iso
|
||||
|
||||
from .base import AndroidQFModule
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
from typing import Optional, List, Dict, Union, Any
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from mvt.android.modules.adb.dumpsys_receivers import (
|
||||
INTENT_DATA_SMS_RECEIVED, INTENT_NEW_OUTGOING_CALL,
|
||||
|
||||
@@ -7,7 +7,7 @@ import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from mvt.android.parsers import getprop
|
||||
from mvt.android.parsers.getprop import parse_getprop
|
||||
|
||||
from .base import AndroidQFModule
|
||||
|
||||
@@ -41,7 +41,17 @@ class Getprop(AndroidQFModule):
|
||||
super().__init__(file_path=file_path, target_path=target_path,
|
||||
results_path=results_path, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
self.results = {}
|
||||
self.results = []
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
ioc = self.indicators.check_android_property_name(result.get("name", ""))
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self) -> None:
|
||||
getprop_files = self._get_files_by_pattern("*/getprop.txt")
|
||||
@@ -52,15 +62,15 @@ class Getprop(AndroidQFModule):
|
||||
with open(getprop_files[0]) as f:
|
||||
data = f.read()
|
||||
|
||||
self.results = getprop.parse_getprop(data)
|
||||
self.results = parse_getprop(data)
|
||||
for entry in self.results:
|
||||
if entry in INTERESTING_PROPERTIES:
|
||||
self.log.info("%s: %s", entry, self.results[entry])
|
||||
if entry == "ro.build.version.security_patch":
|
||||
last_patch = datetime.strptime(self.results[entry], "%Y-%m-%d")
|
||||
if entry["name"] in INTERESTING_PROPERTIES:
|
||||
self.log.info("%s: %s", entry["name"], entry["value"])
|
||||
if entry["name"] == "ro.build.version.security_patch":
|
||||
last_patch = datetime.strptime(entry["value"], "%Y-%m-%d")
|
||||
if (datetime.now() - last_patch) > timedelta(days=6*31):
|
||||
self.log.warning("This phone has not received security "
|
||||
"updates for more than six months "
|
||||
"(last update: %s)", self.results[entry])
|
||||
"(last update: %s)", entry["value"])
|
||||
|
||||
self.log.info("Extracted a total of %d properties", len(self.results))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Mobile Verification Toolkit (MVT) - Private
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# This file is part of MVT Private and its content is confidential.
|
||||
# Please refer to the project maintainers before sharing with others.
|
||||
# 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 logging
|
||||
@@ -38,7 +38,7 @@ class SMS(AndroidQFModule):
|
||||
if "body" not in message:
|
||||
continue
|
||||
|
||||
if self.indicators.check_domains(message["links"]):
|
||||
if self.indicators.check_domains(message.get("links", [])):
|
||||
self.detected.append(message)
|
||||
|
||||
def parse_backup(self, data):
|
||||
|
||||
@@ -7,7 +7,7 @@ import fnmatch
|
||||
import logging
|
||||
import os
|
||||
from tarfile import TarFile
|
||||
from typing import Optional, List
|
||||
from typing import List, Optional
|
||||
|
||||
from mvt.common.module import MVTModule
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Optional
|
||||
|
||||
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(BackupExtraction):
|
||||
@@ -34,7 +35,11 @@ class SMS(BackupExtraction):
|
||||
if "body" not in message:
|
||||
continue
|
||||
|
||||
if self.indicators.check_domains(message["links"]):
|
||||
message_links = message.get("links", [])
|
||||
if message_links == []:
|
||||
message_links = check_for_links(message.get("text", ""))
|
||||
|
||||
if self.indicators.check_domains(message_links):
|
||||
self.detected.append(message)
|
||||
|
||||
def run(self) -> None:
|
||||
@@ -50,5 +55,5 @@ class SMS(BackupExtraction):
|
||||
data = self._get_file_content(file)
|
||||
self.results.extend(parse_sms_file(data))
|
||||
|
||||
self.log.info("Extracted a total of %d SMS & MMS messages containing links",
|
||||
self.log.info("Extracted a total of %d SMS & MMS messages",
|
||||
len(self.results))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, List
|
||||
from typing import List, Optional
|
||||
from zipfile import ZipFile
|
||||
|
||||
from mvt.common.module import MVTModule
|
||||
|
||||
@@ -39,8 +39,9 @@ class Getprop(BugReportModule):
|
||||
|
||||
lines = []
|
||||
in_getprop = False
|
||||
|
||||
for line in content.decode(errors="ignore").splitlines():
|
||||
if line.strip() == "------ SYSTEM PROPERTIES (getprop) ------":
|
||||
if line.strip().startswith("------ SYSTEM PROPERTIES"):
|
||||
in_getprop = True
|
||||
continue
|
||||
|
||||
@@ -55,13 +56,14 @@ 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)
|
||||
for entry in self.results:
|
||||
if entry["name"] == "ro.build.version.security_patch":
|
||||
security_patch = entry["value"]
|
||||
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))
|
||||
|
||||
@@ -218,10 +218,9 @@ def parse_sms_file(data):
|
||||
entry["isodate"] = convert_unix_to_iso(int(entry["date"]) / 1000)
|
||||
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.
|
||||
# Extract links from the body
|
||||
if message_links or entry["body"].strip() == "":
|
||||
entry["links"] = message_links
|
||||
res.append(entry)
|
||||
res.append(entry)
|
||||
|
||||
return res
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import re
|
||||
from typing import List, Dict, Any
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from mvt.common.utils import convert_datetime_to_iso
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import re
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def parse_getprop(output: str) -> dict:
|
||||
results = {}
|
||||
def parse_getprop(output: str) -> List[Dict[str, str]]:
|
||||
results = []
|
||||
rxp = re.compile(r"\[(.+?)\]: \[(.+?)\]")
|
||||
|
||||
for line in output.splitlines():
|
||||
@@ -19,8 +20,10 @@ def parse_getprop(output: str) -> dict:
|
||||
if not matches or len(matches[0]) != 2:
|
||||
continue
|
||||
|
||||
key = matches[0][0]
|
||||
value = matches[0][1]
|
||||
results[key] = value
|
||||
entry = {
|
||||
"name": matches[0][0],
|
||||
"value": matches[0][1]
|
||||
}
|
||||
results.append(entry)
|
||||
|
||||
return results
|
||||
|
||||
@@ -7,6 +7,7 @@ import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from mvt.common.module import PostAnalysisModule
|
||||
from mvt.common.command import Command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -30,7 +31,9 @@ class CmdCheckIOCS(Command):
|
||||
self.name = "check-iocs"
|
||||
|
||||
def run(self) -> None:
|
||||
assert self.target_path is not None
|
||||
all_modules = []
|
||||
post_modules = []
|
||||
for entry in self.modules:
|
||||
if entry not in all_modules:
|
||||
all_modules.append(entry)
|
||||
@@ -42,18 +45,24 @@ class CmdCheckIOCS(Command):
|
||||
name_only, _ = os.path.splitext(file_name)
|
||||
file_path = os.path.join(self.target_path, file_name)
|
||||
|
||||
for iocs_module in all_modules:
|
||||
if self.module_name and iocs_module.__name__ != self.module_name:
|
||||
for module in all_modules:
|
||||
if self.module_name and module.__name__ != self.module_name:
|
||||
continue
|
||||
|
||||
if iocs_module().get_slug() != name_only:
|
||||
# Handle post-analysis modules at the end
|
||||
if issubclass(module, PostAnalysisModule) and module not in post_modules:
|
||||
post_modules.append(module)
|
||||
continue
|
||||
|
||||
# Skip if the current result file does not match the module name
|
||||
if module().get_slug() != name_only:
|
||||
continue
|
||||
|
||||
log.info("Loading results from \"%s\" with module %s",
|
||||
file_name, iocs_module.__name__)
|
||||
file_name, module.__name__)
|
||||
|
||||
m = iocs_module.from_json(file_path,
|
||||
log=logging.getLogger(iocs_module.__module__))
|
||||
m = module.from_json(file_path,
|
||||
log=logging.getLogger(module.__module__))
|
||||
if self.iocs.total_ioc_count > 0:
|
||||
m.indicators = self.iocs
|
||||
m.indicators.log = m.log
|
||||
@@ -65,6 +74,13 @@ class CmdCheckIOCS(Command):
|
||||
else:
|
||||
total_detections += len(m.detected)
|
||||
|
||||
# Run post-analysis modules at end
|
||||
for post_module in post_modules:
|
||||
m = post_module.from_results(self.target_path, log=log)
|
||||
m.run()
|
||||
total_detections += len(m.detected)
|
||||
|
||||
|
||||
if total_detections > 0:
|
||||
log.warning("The check of the results produced %d detections!",
|
||||
total_detections)
|
||||
|
||||
@@ -3,17 +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 hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Callable, Optional
|
||||
from typing import Optional
|
||||
|
||||
from mvt.common.indicators import Indicators
|
||||
from mvt.common.module import run_module, save_timeline, MVTModule
|
||||
from mvt.common.utils import convert_datetime_to_iso, generate_hashes_from_path, get_sha256_from_file_path
|
||||
from mvt.common.module import MVTModule, run_module, save_timeline
|
||||
from mvt.common.utils import (convert_datetime_to_iso,
|
||||
generate_hashes_from_path,
|
||||
get_sha256_from_file_path)
|
||||
from mvt.common.version import MVT_VERSION
|
||||
|
||||
|
||||
@@ -32,6 +33,7 @@ class Command:
|
||||
) -> None:
|
||||
self.name = ""
|
||||
self.modules = []
|
||||
self.modules_post = []
|
||||
|
||||
self.target_path = target_path
|
||||
self.results_path = results_path
|
||||
@@ -41,20 +43,21 @@ class Command:
|
||||
self.fast_mode = fast_mode
|
||||
self.log = log
|
||||
|
||||
self.iocs = Indicators(log=log)
|
||||
self.iocs.load_indicators_files(self.ioc_files)
|
||||
|
||||
# This list will contain all executed modules.
|
||||
# We can use this to reference e.g. self.executed[0].results.
|
||||
self.executed = []
|
||||
|
||||
self.detected_count = 0
|
||||
|
||||
self.hashes = hashes
|
||||
self.hash_values = []
|
||||
self.timeline = []
|
||||
self.timeline_detected = []
|
||||
|
||||
# Load IOCs
|
||||
self._create_storage()
|
||||
self._setup_logging()
|
||||
self.iocs = Indicators(log=log)
|
||||
self.iocs.load_indicators_files(self.ioc_files)
|
||||
|
||||
def _create_storage(self) -> None:
|
||||
if self.results_path and not os.path.exists(self.results_path):
|
||||
try:
|
||||
@@ -64,10 +67,11 @@ class Command:
|
||||
self.results_path, exc)
|
||||
sys.exit(1)
|
||||
|
||||
def _add_log_file_handler(self, logger: logging.Logger) -> None:
|
||||
def _setup_logging(self):
|
||||
if not self.results_path:
|
||||
return
|
||||
|
||||
logger = logging.getLogger("mvt")
|
||||
file_handler = logging.FileHandler(os.path.join(self.results_path,
|
||||
"command.log"))
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - "
|
||||
@@ -121,7 +125,7 @@ class Command:
|
||||
|
||||
if self.target_path and (os.environ.get("MVT_HASH_FILES") or self.hashes):
|
||||
info_hash = get_sha256_from_file_path(info_path)
|
||||
self.log.warning("Reference hash of the info.json file : %s", info_hash)
|
||||
self.log.info("Reference hash of the info.json file: \"%s\"", info_hash)
|
||||
|
||||
def generate_hashes(self) -> None:
|
||||
"""
|
||||
@@ -136,7 +140,7 @@ class Command:
|
||||
def list_modules(self) -> None:
|
||||
self.log.info("Following is the list of available %s modules:",
|
||||
self.name)
|
||||
for module in self.modules:
|
||||
for module in (self.modules + self.modules_post):
|
||||
self.log.info(" - %s", module.__name__)
|
||||
|
||||
def init(self) -> None:
|
||||
@@ -149,8 +153,6 @@ class Command:
|
||||
raise NotImplementedError
|
||||
|
||||
def run(self) -> None:
|
||||
self._create_storage()
|
||||
self._add_log_file_handler(self.log)
|
||||
|
||||
try:
|
||||
self.init()
|
||||
@@ -161,8 +163,8 @@ class Command:
|
||||
if self.module_name and module.__name__ != self.module_name:
|
||||
continue
|
||||
|
||||
# FIXME: do we need the logger here
|
||||
module_logger = logging.getLogger(module.__module__)
|
||||
self._add_log_file_handler(module_logger)
|
||||
|
||||
m = module(target_path=self.target_path,
|
||||
results_path=self.results_path,
|
||||
|
||||
@@ -10,6 +10,7 @@ HELP_MSG_FAST = "Avoid running time/resource consuming features"
|
||||
HELP_MSG_LIST_MODULES = "Print list of available modules and exit"
|
||||
HELP_MSG_MODULE = "Name of a single module you would like to run instead of all"
|
||||
HELP_MSG_HASHES = "Generate hashes of all the files analyzed"
|
||||
HELP_MSG_VERBOSE = "Verbose mode"
|
||||
|
||||
# Android-specific.
|
||||
HELP_MSG_SERIAL = "Specify a device serial number or HOST:PORT connection string"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, Union
|
||||
from typing import Any, Dict, Iterator, List, Optional, Union
|
||||
|
||||
from appdirs import user_data_dir
|
||||
|
||||
@@ -15,15 +15,17 @@ from .url import URL
|
||||
MVT_DATA_FOLDER = user_data_dir("mvt")
|
||||
MVT_INDICATORS_FOLDER = os.path.join(MVT_DATA_FOLDER, "indicators")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Indicators:
|
||||
"""This class is used to parse indicators from a STIX2 file and provide
|
||||
functions to compare extracted artifacts to the indicators.
|
||||
"""
|
||||
|
||||
def __init__(self, log=logging.Logger) -> None:
|
||||
def __init__(self, log=logger) -> None:
|
||||
self.log = log
|
||||
self.ioc_collections = []
|
||||
self.ioc_collections: List[Dict[str, Any]] = []
|
||||
self.total_ioc_count = 0
|
||||
|
||||
def _load_downloaded_indicators(self) -> None:
|
||||
@@ -72,6 +74,7 @@ class Indicators:
|
||||
"files_sha256": [],
|
||||
"app_ids": [],
|
||||
"ios_profile_ids": [],
|
||||
"android_property_names": [],
|
||||
"count": 0,
|
||||
}
|
||||
|
||||
@@ -121,6 +124,11 @@ class Indicators:
|
||||
ioc_coll=collection,
|
||||
ioc_coll_list=collection["ios_profile_ids"])
|
||||
|
||||
elif key == "android-property:name":
|
||||
self._add_indicator(ioc=value,
|
||||
ioc_coll=collection,
|
||||
ioc_coll_list=collection["android_property_names"])
|
||||
|
||||
def parse_stix2(self, file_path: str) -> None:
|
||||
"""Extract indicators from a STIX2 file.
|
||||
|
||||
@@ -209,7 +217,7 @@ class Indicators:
|
||||
self.log.info("Loaded a total of %d unique indicators",
|
||||
self.total_ioc_count)
|
||||
|
||||
def get_iocs(self, ioc_type: str) -> Union[dict, None]:
|
||||
def get_iocs(self, ioc_type: str) -> Iterator[Dict[str, Any]]:
|
||||
for ioc_collection in self.ioc_collections:
|
||||
for ioc in ioc_collection.get(ioc_type, []):
|
||||
yield {
|
||||
@@ -227,10 +235,10 @@ class Indicators:
|
||||
:returns: Indicator details if matched, otherwise None
|
||||
|
||||
"""
|
||||
# TODO: If the IOC domain contains a subdomain, it is not currently
|
||||
# being matched.
|
||||
if not url:
|
||||
return None
|
||||
if not isinstance(url, str):
|
||||
return None
|
||||
|
||||
try:
|
||||
# First we use the provided URL.
|
||||
@@ -241,15 +249,17 @@ class Indicators:
|
||||
# HTTP HEAD request.
|
||||
unshortened = orig_url.unshorten()
|
||||
|
||||
# self.log.info("Found a shortened URL %s -> %s",
|
||||
# url, unshortened)
|
||||
self.log.debug("Found a shortened URL %s -> %s",
|
||||
url, unshortened)
|
||||
if unshortened is None:
|
||||
return None
|
||||
|
||||
# Now we check for any nested URL shorteners.
|
||||
dest_url = URL(unshortened)
|
||||
if dest_url.check_if_shortened():
|
||||
# self.log.info("Original URL %s appears to shorten another "
|
||||
# "shortened URL %s ... checking!",
|
||||
# orig_url.url, dest_url.url)
|
||||
self.log.debug("Original URL %s appears to shorten another "
|
||||
"shortened URL %s ... checking!",
|
||||
orig_url.url, dest_url.url)
|
||||
return self.check_domain(dest_url.url)
|
||||
|
||||
final_url = dest_url
|
||||
@@ -436,7 +446,7 @@ class Indicators:
|
||||
|
||||
return None
|
||||
|
||||
def check_file_path_process(self, file_path: str) -> Union[dict, None]:
|
||||
def check_file_path_process(self, file_path: str) -> Optional[Dict[str, Any]]:
|
||||
"""Check the provided file path contains a process name from the
|
||||
list of indicators
|
||||
|
||||
@@ -457,6 +467,8 @@ class Indicators:
|
||||
file_path, ioc["name"])
|
||||
return ioc
|
||||
|
||||
return None
|
||||
|
||||
def check_profile(self, profile_uuid: str) -> Union[dict, None]:
|
||||
"""Check the provided configuration profile UUID against the list of
|
||||
indicators.
|
||||
@@ -519,3 +531,23 @@ class Indicators:
|
||||
return ioc
|
||||
|
||||
return None
|
||||
|
||||
def check_android_property_name(self, property_name: str) -> Optional[dict]:
|
||||
"""Check the android property name against the list of indicators.
|
||||
|
||||
:param property_name: Name of the Android property
|
||||
:type property_name: str
|
||||
:returns: Indicator details if matched, otherwise None
|
||||
|
||||
"""
|
||||
if property_name is None:
|
||||
return None
|
||||
|
||||
for ioc in self.get_iocs("android_property_names"):
|
||||
if property_name.lower() == ioc["value"].lower():
|
||||
self.log.warning("Found a known suspicious Android property \"%s\" "
|
||||
"matching indicators from \"%s\"", property_name,
|
||||
ioc["name"])
|
||||
return ioc
|
||||
|
||||
return None
|
||||
|
||||
@@ -7,7 +7,8 @@ import csv
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Callable, Optional, Union, List, Any, Dict
|
||||
import glob
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import simplejson as json
|
||||
|
||||
@@ -28,7 +29,7 @@ class MVTModule:
|
||||
"""This class provides a base for all extraction modules."""
|
||||
|
||||
enabled = True
|
||||
slug = None
|
||||
slug: Optional[str] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -61,12 +62,12 @@ class MVTModule:
|
||||
self.log = log
|
||||
self.indicators = None
|
||||
self.results = results if results else []
|
||||
self.detected = []
|
||||
self.timeline = []
|
||||
self.timeline_detected = []
|
||||
self.detected: List[Dict[str, Any]] = []
|
||||
self.timeline: List[Dict[str, str]] = []
|
||||
self.timeline_detected: List[Dict[str, str]] = []
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_path: str, log: logging.Logger = None):
|
||||
def from_json(cls, json_path: str, log: logging.Logger):
|
||||
with open(json_path, "r", encoding="utf-8") as handle:
|
||||
results = json.load(handle)
|
||||
if log:
|
||||
@@ -116,7 +117,7 @@ class MVTModule:
|
||||
with open(detected_json_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(self.detected, handle, indent=4, default=str)
|
||||
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
def serialize(self, record: dict) -> Union[dict, list, None]:
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
@@ -159,7 +160,7 @@ class MVTModule:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def run_module(module: Callable) -> None:
|
||||
def run_module(module: MVTModule) -> None:
|
||||
module.log.info("Running module %s...", module.__class__.__name__)
|
||||
|
||||
try:
|
||||
@@ -185,6 +186,10 @@ def run_module(module: Callable) -> None:
|
||||
except NotImplementedError:
|
||||
module.log.info("The %s module does not support checking for indicators",
|
||||
module.__class__.__name__)
|
||||
except Exception as exc:
|
||||
module.log.exception("Error when checking indicators from module %s: %s",
|
||||
module.__class__.__name__, exc)
|
||||
|
||||
else:
|
||||
if module.indicators and not module.detected:
|
||||
module.log.info("The %s module produced no detections!",
|
||||
@@ -194,6 +199,9 @@ def run_module(module: Callable) -> None:
|
||||
module.to_timeline()
|
||||
except NotImplementedError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
module.log.exception("Error when serializing data from module %s: %s",
|
||||
module.__class__.__name__, exc)
|
||||
|
||||
module.save_to_json()
|
||||
|
||||
@@ -207,7 +215,7 @@ def save_timeline(timeline: list, timeline_path: str) -> None:
|
||||
"""
|
||||
with open(timeline_path, "a+", encoding="utf-8") as handle:
|
||||
csvoutput = csv.writer(handle, delimiter=",", quotechar="\"",
|
||||
quoting=csv.QUOTE_ALL)
|
||||
quoting=csv.QUOTE_ALL, escapechar='\\')
|
||||
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
|
||||
|
||||
for event in sorted(timeline, key=lambda x: x["timestamp"]
|
||||
@@ -218,3 +226,51 @@ def save_timeline(timeline: list, timeline_path: str) -> None:
|
||||
event.get("event"),
|
||||
event.get("data"),
|
||||
])
|
||||
|
||||
|
||||
class PostAnalysisModule(MVTModule):
|
||||
"""
|
||||
Base module for implementing post-processing rules against the output of
|
||||
multiple MVT modules
|
||||
"""
|
||||
@classmethod
|
||||
def from_results(cls, results_path: str, log: logging.Logger):
|
||||
results = cls.load_results(results_path, log=log)
|
||||
return cls(results=results, log=log)
|
||||
|
||||
@classmethod
|
||||
def load_results(cls, results_path: str, log: logging.Logger):
|
||||
"""Load the results from a directory of json file."""
|
||||
# TODO: Move this to run once before loading all post-processing modules
|
||||
module_results = {}
|
||||
for json_path in glob.glob(os.path.join(results_path, "*.json")):
|
||||
module_name, _ = os.path.splitext(os.path.basename(json_path))
|
||||
with open(json_path, "r", encoding="utf-8") as handle:
|
||||
try:
|
||||
module_results[module_name] = json.load(handle)
|
||||
except Exception as exc:
|
||||
log.error("Unable to load results from file %s: %s",
|
||||
json_path, exc)
|
||||
|
||||
if not module_results:
|
||||
log.error("Did not find any MVT results at %s", results_path)
|
||||
|
||||
return module_results
|
||||
|
||||
def load_timeline(self):
|
||||
"""Load timeline from CSV file"""
|
||||
timeline = []
|
||||
timeline_path = os.path.join(self.results_path, "timeline.csv")
|
||||
with open(timeline_path, "r", encoding="utf-8") as handle:
|
||||
csvinput = csv.reader(handle, delimiter=",", quotechar="\"",
|
||||
quoting=csv.QUOTE_ALL, escapechar='\\')
|
||||
for row in csvinput:
|
||||
if row[0] == "UTC Timestamp":
|
||||
continue
|
||||
timeline.append({
|
||||
"timestamp": row[0],
|
||||
"module": row[1],
|
||||
"event": row[2],
|
||||
"data": row[3],
|
||||
})
|
||||
return timeline
|
||||
@@ -6,6 +6,7 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
@@ -83,7 +84,7 @@ class IndicatorsUpdates:
|
||||
with open(self.latest_update_path, "w", encoding="utf-8") as handle:
|
||||
handle.write(str(timestamp))
|
||||
|
||||
def get_remote_index(self) -> dict:
|
||||
def get_remote_index(self) -> Optional[dict]:
|
||||
url = self.github_raw_url.format(self.index_owner, self.index_repo,
|
||||
self.index_branch, self.index_path)
|
||||
res = requests.get(url)
|
||||
@@ -94,7 +95,7 @@ class IndicatorsUpdates:
|
||||
|
||||
return yaml.safe_load(res.content)
|
||||
|
||||
def download_remote_ioc(self, ioc_url: str) -> str:
|
||||
def download_remote_ioc(self, ioc_url: str) -> Optional[str]:
|
||||
res = requests.get(ioc_url)
|
||||
if res.status_code != 200:
|
||||
log.error("Failed to download indicators file from %s (error %d)",
|
||||
@@ -116,6 +117,9 @@ class IndicatorsUpdates:
|
||||
os.makedirs(MVT_INDICATORS_FOLDER)
|
||||
|
||||
index = self.get_remote_index()
|
||||
if not index:
|
||||
return
|
||||
|
||||
for ioc in index.get("indicators", []):
|
||||
ioc_type = ioc.get("type", "")
|
||||
|
||||
@@ -171,7 +175,7 @@ class IndicatorsUpdates:
|
||||
|
||||
return latest_commit_ts
|
||||
|
||||
def should_check(self) -> (bool, int):
|
||||
def should_check(self) -> Tuple[bool, int]:
|
||||
now = datetime.utcnow()
|
||||
latest_check_ts = self.get_latest_check()
|
||||
latest_check_dt = datetime.fromtimestamp(latest_check_ts)
|
||||
@@ -182,7 +186,7 @@ class IndicatorsUpdates:
|
||||
if diff_hours >= INDICATORS_CHECK_FREQUENCY:
|
||||
return True, 0
|
||||
|
||||
return False, INDICATORS_CHECK_FREQUENCY - diff_hours
|
||||
return False, int(INDICATORS_CHECK_FREQUENCY - diff_hours)
|
||||
|
||||
def check(self) -> bool:
|
||||
self.set_latest_check()
|
||||
@@ -197,6 +201,9 @@ class IndicatorsUpdates:
|
||||
return True
|
||||
|
||||
index = self.get_remote_index()
|
||||
if not index:
|
||||
return False
|
||||
|
||||
for ioc in index.get("indicators", []):
|
||||
if ioc.get("type", "") != "github":
|
||||
continue
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Union, Iterator
|
||||
from typing import Any, Iterator, Union
|
||||
|
||||
from rich.logging import RichHandler
|
||||
|
||||
|
||||
def convert_chrometime_to_datetime(timestamp: int) -> datetime.datetime:
|
||||
@@ -51,7 +54,7 @@ def convert_unix_to_utc_datetime(
|
||||
return datetime.datetime.utcfromtimestamp(float(timestamp))
|
||||
|
||||
|
||||
def convert_unix_to_iso(timestamp: int) -> str:
|
||||
def convert_unix_to_iso(timestamp: Union[int, float, str]) -> str:
|
||||
"""Converts a unix epoch to ISO string.
|
||||
|
||||
:param timestamp: Epoc timestamp to convert.
|
||||
@@ -125,7 +128,7 @@ def check_for_links(text: str) -> list:
|
||||
|
||||
# Note: taken from here:
|
||||
# https://stackoverflow.com/questions/57014259/json-dumps-on-dictionary-with-bytes-for-keys
|
||||
def keys_bytes_to_string(obj) -> str:
|
||||
def keys_bytes_to_string(obj: Any) -> Any:
|
||||
"""Convert object keys from bytes to string.
|
||||
|
||||
:param obj: Object to convert from bytes to string.
|
||||
@@ -161,9 +164,12 @@ def get_sha256_from_file_path(file_path: str) -> str:
|
||||
|
||||
"""
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(file_path, "rb") as handle:
|
||||
for byte_block in iter(lambda: handle.read(4096), b""):
|
||||
sha256_hash.update(byte_block)
|
||||
try:
|
||||
with open(file_path, "rb") as handle:
|
||||
for byte_block in iter(lambda: handle.read(4096), b""):
|
||||
sha256_hash.update(byte_block)
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
@@ -194,3 +200,28 @@ def generate_hashes_from_path(path: str, log) -> Iterator[dict]:
|
||||
continue
|
||||
|
||||
yield {"file_path": file_path, "sha256": sha256}
|
||||
|
||||
|
||||
def init_logging(verbose: bool = False):
|
||||
"""
|
||||
Initialise logging for the MVT module
|
||||
"""
|
||||
# Setup logging using Rich.
|
||||
log = logging.getLogger("mvt")
|
||||
log.setLevel(logging.DEBUG)
|
||||
consoleHandler = RichHandler(show_path=False, log_time_format="%X")
|
||||
consoleHandler.setFormatter(logging.Formatter("[%(name)s] %(message)s"))
|
||||
if verbose:
|
||||
consoleHandler.setLevel(logging.DEBUG)
|
||||
else:
|
||||
consoleHandler.setLevel(logging.INFO)
|
||||
log.addHandler(consoleHandler)
|
||||
|
||||
|
||||
def set_verbose_logging(verbose: bool = False):
|
||||
log = logging.getLogger("mvt")
|
||||
handler = log.handlers[0]
|
||||
if verbose:
|
||||
handler.setLevel(logging.DEBUG)
|
||||
else:
|
||||
handler.setLevel(logging.INFO)
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
MVT_VERSION = "2.2.2"
|
||||
MVT_VERSION = "2.2.6"
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
# 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 logging
|
||||
import os
|
||||
import json
|
||||
|
||||
import click
|
||||
from rich.logging import RichHandler
|
||||
from rich.prompt import Prompt
|
||||
|
||||
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
||||
from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_IOC,
|
||||
from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_HASHES, HELP_MSG_IOC,
|
||||
HELP_MSG_LIST_MODULES, HELP_MSG_MODULE,
|
||||
HELP_MSG_OUTPUT, HELP_MSG_HASHES)
|
||||
HELP_MSG_OUTPUT, HELP_MSG_VERBOSE)
|
||||
from mvt.common.logo import logo
|
||||
from mvt.common.options import MutuallyExclusiveOption
|
||||
from mvt.common.updates import IndicatorsUpdates
|
||||
from mvt.common.utils import generate_hashes_from_path
|
||||
from mvt.common.utils import (generate_hashes_from_path, init_logging,
|
||||
set_verbose_logging)
|
||||
|
||||
from .cmd_check_backup import CmdIOSCheckBackup
|
||||
from .cmd_check_fs import CmdIOSCheckFS
|
||||
@@ -26,12 +26,10 @@ from .decrypt import DecryptBackup
|
||||
from .modules.backup import BACKUP_MODULES
|
||||
from .modules.fs import FS_MODULES
|
||||
from .modules.mixed import MIXED_MODULES
|
||||
from .modules.post_analysis import POST_ANALYSIS_MODULES
|
||||
|
||||
# Setup logging using Rich.
|
||||
LOG_FORMAT = "[%(name)s] %(message)s"
|
||||
logging.basicConfig(level="INFO", format=LOG_FORMAT, handlers=[
|
||||
RichHandler(show_path=False, log_time_format="%X")])
|
||||
log = logging.getLogger(__name__)
|
||||
init_logging()
|
||||
log = logging.getLogger("mvt")
|
||||
|
||||
# Set this environment variable to a password if needed.
|
||||
MVT_IOS_BACKUP_PASSWORD = "MVT_IOS_BACKUP_PASSWORD"
|
||||
@@ -166,9 +164,12 @@ def extract_key(password, key_file, backup_path):
|
||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_backup(ctx, iocs, output, fast, list_modules, module, hashes, backup_path):
|
||||
def check_backup(ctx, iocs, output, fast, list_modules, module, hashes, verbose, backup_path):
|
||||
set_verbose_logging(verbose)
|
||||
|
||||
cmd = CmdIOSCheckBackup(target_path=backup_path, results_path=output,
|
||||
ioc_files=iocs, module_name=module, fast_mode=fast,
|
||||
hashes=hashes)
|
||||
@@ -199,9 +200,11 @@ def check_backup(ctx, iocs, output, fast, list_modules, module, hashes, backup_p
|
||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE)
|
||||
@click.argument("DUMP_PATH", type=click.Path(exists=True))
|
||||
@click.pass_context
|
||||
def check_fs(ctx, iocs, output, fast, list_modules, module, hashes, dump_path):
|
||||
def check_fs(ctx, iocs, output, fast, list_modules, module, hashes, verbose, dump_path):
|
||||
set_verbose_logging(verbose)
|
||||
cmd = CmdIOSCheckFS(target_path=dump_path, results_path=output,
|
||||
ioc_files=iocs, module_name=module, fast_mode=fast,
|
||||
hashes=hashes)
|
||||
@@ -232,7 +235,7 @@ def check_fs(ctx, iocs, output, fast, list_modules, module, hashes, dump_path):
|
||||
@click.pass_context
|
||||
def check_iocs(ctx, iocs, list_modules, module, folder):
|
||||
cmd = CmdCheckIOCS(target_path=folder, ioc_files=iocs, module_name=module)
|
||||
cmd.modules = BACKUP_MODULES + FS_MODULES + MIXED_MODULES
|
||||
cmd.modules = BACKUP_MODULES + FS_MODULES + MIXED_MODULES + POST_ANALYSIS_MODULES
|
||||
|
||||
if list_modules:
|
||||
cmd.list_modules()
|
||||
|
||||
166
mvt/ios/data/ios_models.json
Normal file
166
mvt/ios/data/ios_models.json
Normal file
@@ -0,0 +1,166 @@
|
||||
[
|
||||
{
|
||||
"identifier": "iPhone4,1",
|
||||
"description": "iPhone 4S"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone5,1",
|
||||
"description": "iPhone 5"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone5,2",
|
||||
"description": "iPhone 5"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone5,3",
|
||||
"description": "iPhone 5c"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone5,4",
|
||||
"description": "iPhone 5c"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone6,1",
|
||||
"description": "iPhone 5s"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone6,2",
|
||||
"description": "iPhone 5s"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone7,1",
|
||||
"description": "iPhone 6 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone7,2",
|
||||
"description": "iPhone 6"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone8,1",
|
||||
"description": "iPhone 6s"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone8,2",
|
||||
"description": "iPhone 6s Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone8,4",
|
||||
"description": "iPhone SE (1st generation)"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone9,1",
|
||||
"description": "iPhone 7"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone9,2",
|
||||
"description": "iPhone 7 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone9,3",
|
||||
"description": "iPhone 7"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone9,4",
|
||||
"description": "iPhone 7 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone10,1",
|
||||
"description": "iPhone 8"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone10,2",
|
||||
"description": "iPhone 8 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone10,3",
|
||||
"description": "iPhone X"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone10,4",
|
||||
"description": "iPhone 8"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone10,5",
|
||||
"description": "iPhone 8 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone10,6",
|
||||
"description": "iPhone X"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone11,2",
|
||||
"description": "iPhone XS"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone11,4",
|
||||
"description": "iPhone XS Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone11,6",
|
||||
"description": "iPhone XS Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone11,8",
|
||||
"description": "iPhone XR"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone12,1",
|
||||
"description": "iPhone 11"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone12,3",
|
||||
"description": "iPhone 11 Pro"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone12,5",
|
||||
"description": "iPhone 11 Pro Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone12,8",
|
||||
"description": "iPhone SE (2nd generation)"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone13,1",
|
||||
"description": "iPhone 12 mini"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone13,2",
|
||||
"description": "iPhone 12"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone13,3",
|
||||
"description": "iPhone 12 Pro"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone13,4",
|
||||
"description": "iPhone 12 Pro Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone14,4",
|
||||
"description": "iPhone 13 Mini"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone14,5",
|
||||
"description": "iPhone 13"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone14,2",
|
||||
"description": "iPhone 13 Pro"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone14,3",
|
||||
"description": "iPhone 13 Pro Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone14,8",
|
||||
"decription": "iPhone 14 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone15,2",
|
||||
"description": "iPhone 14 Pro"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone15,3",
|
||||
"description": "iPhone 14 Pro Max"
|
||||
}
|
||||
]
|
||||
923
mvt/ios/data/ios_versions.json
Normal file
923
mvt/ios/data/ios_versions.json
Normal file
@@ -0,0 +1,923 @@
|
||||
[
|
||||
{
|
||||
"build": "1A543a",
|
||||
"version": "1.0"
|
||||
},
|
||||
{
|
||||
"build": "1C25",
|
||||
"version": "1.0.1"
|
||||
},
|
||||
{
|
||||
"build": "1C28",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"build": "3A109a",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"build": "3B48b",
|
||||
"version": "1.1.2"
|
||||
},
|
||||
{
|
||||
"build": "4A93",
|
||||
"version": "1.1.3"
|
||||
},
|
||||
{
|
||||
"build": "4A102",
|
||||
"version": "1.1.4"
|
||||
},
|
||||
{
|
||||
"build": "5A347",
|
||||
"version": "2.0"
|
||||
},
|
||||
{
|
||||
"build": "5B108",
|
||||
"version": "2.0.1"
|
||||
},
|
||||
{
|
||||
"build": "5C1",
|
||||
"version": "2.0.2"
|
||||
},
|
||||
{
|
||||
"build": "5F136",
|
||||
"version": "2.1"
|
||||
},
|
||||
{
|
||||
"build": "5G77",
|
||||
"version": "2.2"
|
||||
},
|
||||
{
|
||||
"build": "5H11",
|
||||
"version": "2.2.1"
|
||||
},
|
||||
{
|
||||
"build": "7A341",
|
||||
"version": "3.0"
|
||||
},
|
||||
{
|
||||
"build": "7A400",
|
||||
"version": "3.0.1"
|
||||
},
|
||||
{
|
||||
"build": "7C144",
|
||||
"version": "3.1"
|
||||
},
|
||||
{
|
||||
"build": "7D11",
|
||||
"version": "3.1.2"
|
||||
},
|
||||
{
|
||||
"build": "7E18",
|
||||
"version": "3.1.3"
|
||||
},
|
||||
{
|
||||
"build": "8A293",
|
||||
"version": "4.0"
|
||||
},
|
||||
{
|
||||
"build": "8A306",
|
||||
"version": "4.0.1"
|
||||
},
|
||||
{
|
||||
"build": "8B117",
|
||||
"version": "4.1"
|
||||
},
|
||||
{
|
||||
"build": "8C148",
|
||||
"version": "4.2"
|
||||
},
|
||||
{
|
||||
"build": "8C148a",
|
||||
"version": "4.2.1"
|
||||
},
|
||||
{
|
||||
"build": "8C148",
|
||||
"version": "4.2.1"
|
||||
},
|
||||
{
|
||||
"build": "8E401",
|
||||
"version": "4.2.8"
|
||||
},
|
||||
{
|
||||
"build": "8E501",
|
||||
"version": "4.2.9"
|
||||
},
|
||||
{
|
||||
"build": "8E600",
|
||||
"version": "4.2.10"
|
||||
},
|
||||
{
|
||||
"build": "8F190",
|
||||
"version": "4.3"
|
||||
},
|
||||
{
|
||||
"build": "8J2",
|
||||
"version": "4.3.3"
|
||||
},
|
||||
{
|
||||
"build": "8K2",
|
||||
"version": "4.3.4"
|
||||
},
|
||||
{
|
||||
"build": "8L1",
|
||||
"version": "4.3.5"
|
||||
},
|
||||
{
|
||||
"build": "9A334",
|
||||
"version": "5.0"
|
||||
},
|
||||
{
|
||||
"build": "9A405",
|
||||
"version": "5.0.1"
|
||||
},
|
||||
{
|
||||
"build": "9A406",
|
||||
"version": "5.0.1"
|
||||
},
|
||||
{
|
||||
"build": "9B176",
|
||||
"version": "5.1"
|
||||
},
|
||||
{
|
||||
"build": "9B179",
|
||||
"version": "5.1"
|
||||
},
|
||||
{
|
||||
"build": "9B206",
|
||||
"version": "5.1.1"
|
||||
},
|
||||
{
|
||||
"build": "9B208",
|
||||
"version": "5.1.1"
|
||||
},
|
||||
{
|
||||
"build": "10A403",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"build": "10A405",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"build": "10A523",
|
||||
"version": "6.0.1"
|
||||
},
|
||||
{
|
||||
"build": "10A525",
|
||||
"version": "6.0.1"
|
||||
},
|
||||
{
|
||||
"build": "10A551",
|
||||
"version": "6.0.2"
|
||||
},
|
||||
{
|
||||
"build": "10B141",
|
||||
"version": "6.1"
|
||||
},
|
||||
{
|
||||
"build": "10B144",
|
||||
"version": "6.1"
|
||||
},
|
||||
{
|
||||
"build": "10B142",
|
||||
"version": "6.1"
|
||||
},
|
||||
{
|
||||
"build": "10B143",
|
||||
"version": "6.1"
|
||||
},
|
||||
{
|
||||
"build": "10B145",
|
||||
"version": "6.1.1"
|
||||
},
|
||||
{
|
||||
"build": "10B146",
|
||||
"version": "6.1.2"
|
||||
},
|
||||
{
|
||||
"build": "10B329",
|
||||
"version": "6.1.3"
|
||||
},
|
||||
{
|
||||
"build": "10B350",
|
||||
"version": "6.1.4"
|
||||
},
|
||||
{
|
||||
"build": "10B500",
|
||||
"version": "6.1.6"
|
||||
},
|
||||
{
|
||||
"build": "11B511",
|
||||
"version": "7.0.3"
|
||||
},
|
||||
{
|
||||
"build": "11B554a",
|
||||
"version": "7.0.4"
|
||||
},
|
||||
{
|
||||
"build": "11B601",
|
||||
"version": "7.0.5"
|
||||
},
|
||||
{
|
||||
"build": "11B651",
|
||||
"version": "7.0.6"
|
||||
},
|
||||
{
|
||||
"build": "11D169",
|
||||
"version": "7.1"
|
||||
},
|
||||
{
|
||||
"build": "11D167",
|
||||
"version": "7.1"
|
||||
},
|
||||
{
|
||||
"build": "11D201",
|
||||
"version": "7.1.1"
|
||||
},
|
||||
{
|
||||
"build": "11D257",
|
||||
"version": "7.1.2"
|
||||
},
|
||||
{
|
||||
"build": "12A365",
|
||||
"version": "8.0"
|
||||
},
|
||||
{
|
||||
"build": "12A366",
|
||||
"version": "8.0"
|
||||
},
|
||||
{
|
||||
"build": "12A402",
|
||||
"version": "8.0.1"
|
||||
},
|
||||
{
|
||||
"build": "12A405",
|
||||
"version": "8.0.2"
|
||||
},
|
||||
{
|
||||
"build": "12B411",
|
||||
"version": "8.1"
|
||||
},
|
||||
{
|
||||
"build": "12B435",
|
||||
"version": "8.1.1"
|
||||
},
|
||||
{
|
||||
"build": "12B436",
|
||||
"version": "8.1.1"
|
||||
},
|
||||
{
|
||||
"build": "12B440",
|
||||
"version": "8.1.2"
|
||||
},
|
||||
{
|
||||
"build": "12B466",
|
||||
"version": "8.1.3"
|
||||
},
|
||||
{
|
||||
"build": "12D508",
|
||||
"version": "8.2"
|
||||
},
|
||||
{
|
||||
"build": "12F70",
|
||||
"version": "8.3"
|
||||
},
|
||||
{
|
||||
"build": "12H143",
|
||||
"version": "8.4"
|
||||
},
|
||||
{
|
||||
"build": "12H321",
|
||||
"version": "8.4.1"
|
||||
},
|
||||
{
|
||||
"build": "13A344",
|
||||
"version": "9.0"
|
||||
},
|
||||
{
|
||||
"build": "13A342",
|
||||
"version": "9.0"
|
||||
},
|
||||
{
|
||||
"build": "13A343",
|
||||
"version": "9.0"
|
||||
},
|
||||
{
|
||||
"build": "13A404",
|
||||
"version": "9.0.1"
|
||||
},
|
||||
{
|
||||
"build": "13A405",
|
||||
"version": "9.0.1"
|
||||
},
|
||||
{
|
||||
"build": "13A452",
|
||||
"version": "9.0.2"
|
||||
},
|
||||
{
|
||||
"build": "13B143",
|
||||
"version": "9.1"
|
||||
},
|
||||
{
|
||||
"build": "13C75",
|
||||
"version": "9.2"
|
||||
},
|
||||
{
|
||||
"build": "13D15",
|
||||
"version": "9.2.1"
|
||||
},
|
||||
{
|
||||
"build": "13D20",
|
||||
"version": "9.2.1"
|
||||
},
|
||||
{
|
||||
"build": "13E237",
|
||||
"version": "9.3"
|
||||
},
|
||||
{
|
||||
"build": "13E233",
|
||||
"version": "9.3"
|
||||
},
|
||||
{
|
||||
"build": "13E234",
|
||||
"version": "9.3"
|
||||
},
|
||||
{
|
||||
"build": "13E238",
|
||||
"version": "9.3.1"
|
||||
},
|
||||
{
|
||||
"build": "13F69",
|
||||
"version": "9.3.2"
|
||||
},
|
||||
{
|
||||
"build": "13G34",
|
||||
"version": "9.3.3"
|
||||
},
|
||||
{
|
||||
"build": "13G35",
|
||||
"version": "9.3.4"
|
||||
},
|
||||
{
|
||||
"build": "13G36",
|
||||
"version": "9.3.5"
|
||||
},
|
||||
{
|
||||
"build": "13G37",
|
||||
"version": "9.3.6"
|
||||
},
|
||||
{
|
||||
"build": "14A403",
|
||||
"version": "10.0.1"
|
||||
},
|
||||
{
|
||||
"build": "14A456",
|
||||
"version": "10.0.2"
|
||||
},
|
||||
{
|
||||
"build": "14A551",
|
||||
"version": "10.0.3"
|
||||
},
|
||||
{
|
||||
"build": "14B72",
|
||||
"version": "10.1"
|
||||
},
|
||||
{
|
||||
"build": "14B72c",
|
||||
"version": "10.1"
|
||||
},
|
||||
{
|
||||
"build": "14B150",
|
||||
"version": "10.1.1"
|
||||
},
|
||||
{
|
||||
"build": "14B100",
|
||||
"version": "10.1.1"
|
||||
},
|
||||
{
|
||||
"build": "14C92",
|
||||
"version": "10.2"
|
||||
},
|
||||
{
|
||||
"build": "14D27",
|
||||
"version": "10.2.1"
|
||||
},
|
||||
{
|
||||
"build": "14E277",
|
||||
"version": "10.3"
|
||||
},
|
||||
{
|
||||
"build": "14E304",
|
||||
"version": "10.3.1"
|
||||
},
|
||||
{
|
||||
"build": "14F89",
|
||||
"version": "10.3.2"
|
||||
},
|
||||
{
|
||||
"build": "14G60",
|
||||
"version": "10.3.3"
|
||||
},
|
||||
{
|
||||
"build": "14G61",
|
||||
"version": "10.3.4"
|
||||
},
|
||||
{
|
||||
"build": "15A372",
|
||||
"version": "11.0"
|
||||
},
|
||||
{
|
||||
"build": "15A402",
|
||||
"version": "11.0.1"
|
||||
},
|
||||
{
|
||||
"version": "11.0.1",
|
||||
"build": "15A403"
|
||||
},
|
||||
{
|
||||
"build": "15A421",
|
||||
"version": "11.0.2"
|
||||
},
|
||||
{
|
||||
"build": "15A432",
|
||||
"version": "11.0.3"
|
||||
},
|
||||
{
|
||||
"build": "15B93",
|
||||
"version": "11.1"
|
||||
},
|
||||
{
|
||||
"build": "15B150",
|
||||
"version": "11.1.1"
|
||||
},
|
||||
{
|
||||
"build": "15B202",
|
||||
"version": "11.1.2"
|
||||
},
|
||||
{
|
||||
"build": "15C114",
|
||||
"version": "11.2"
|
||||
},
|
||||
{
|
||||
"build": "15C153",
|
||||
"version": "11.2.1"
|
||||
},
|
||||
{
|
||||
"build": "15C202",
|
||||
"version": "11.2.2"
|
||||
},
|
||||
{
|
||||
"build": "15D60",
|
||||
"version": "11.2.5"
|
||||
},
|
||||
{
|
||||
"build": "15D100",
|
||||
"version": "11.2.6"
|
||||
},
|
||||
{
|
||||
"build": "15E216",
|
||||
"version": "11.3"
|
||||
},
|
||||
{
|
||||
"build": "15E302",
|
||||
"version": "11.3.1"
|
||||
},
|
||||
{
|
||||
"build": "15F79",
|
||||
"version": "11.4"
|
||||
},
|
||||
{
|
||||
"build": "15G77",
|
||||
"version": "11.4.1"
|
||||
},
|
||||
{
|
||||
"build": "16A366",
|
||||
"version": "12.0"
|
||||
},
|
||||
{
|
||||
"build": "16A367",
|
||||
"version": "12.0"
|
||||
},
|
||||
{
|
||||
"build": "16A404",
|
||||
"version": "12.0.1"
|
||||
},
|
||||
{
|
||||
"build": "16A405",
|
||||
"version": "12.0.1"
|
||||
},
|
||||
{
|
||||
"build": "16B92",
|
||||
"version": "12.1"
|
||||
},
|
||||
{
|
||||
"build": "16B94",
|
||||
"version": "12.1"
|
||||
},
|
||||
{
|
||||
"build": "16B93",
|
||||
"version": "12.1"
|
||||
},
|
||||
{
|
||||
"build": "16C50",
|
||||
"version": "12.1.1"
|
||||
},
|
||||
{
|
||||
"build": "16C104",
|
||||
"version": "12.1.2"
|
||||
},
|
||||
{
|
||||
"build": "16C101",
|
||||
"version": "12.1.2"
|
||||
},
|
||||
{
|
||||
"build": "16D39",
|
||||
"version": "12.1.3"
|
||||
},
|
||||
{
|
||||
"build": "16D40",
|
||||
"version": "12.1.3"
|
||||
},
|
||||
{
|
||||
"build": "16D57",
|
||||
"version": "12.1.4"
|
||||
},
|
||||
{
|
||||
"build": "16E227",
|
||||
"version": "12.2"
|
||||
},
|
||||
{
|
||||
"build": "16F156",
|
||||
"version": "12.3"
|
||||
},
|
||||
{
|
||||
"build": "16F203",
|
||||
"version": "12.3.1"
|
||||
},
|
||||
{
|
||||
"build": "16F250",
|
||||
"version": "12.3.2"
|
||||
},
|
||||
{
|
||||
"build": "16G77",
|
||||
"version": "12.4"
|
||||
},
|
||||
{
|
||||
"build": "16G102",
|
||||
"version": "12.4.1"
|
||||
},
|
||||
{
|
||||
"build": "16G114",
|
||||
"version": "12.4.2"
|
||||
},
|
||||
{
|
||||
"build": "16G130",
|
||||
"version": "12.4.3"
|
||||
},
|
||||
{
|
||||
"build": "16G140",
|
||||
"version": "12.4.4"
|
||||
},
|
||||
{
|
||||
"build": "16G161",
|
||||
"version": "12.4.5"
|
||||
},
|
||||
{
|
||||
"build": "16G183",
|
||||
"version": "12.4.6"
|
||||
},
|
||||
{
|
||||
"build": "16G192",
|
||||
"version": "12.4.7"
|
||||
},
|
||||
{
|
||||
"build": "16G201",
|
||||
"version": "12.4.8"
|
||||
},
|
||||
{
|
||||
"build": "16H5",
|
||||
"version": "12.4.9"
|
||||
},
|
||||
{
|
||||
"build": "16H20",
|
||||
"version": "12.5"
|
||||
},
|
||||
{
|
||||
"build": "16H22",
|
||||
"version": "12.5.1"
|
||||
},
|
||||
{
|
||||
"build": "16H30",
|
||||
"version": "12.5.2"
|
||||
},
|
||||
{
|
||||
"build": "16H41",
|
||||
"version": "12.5.3"
|
||||
},
|
||||
{
|
||||
"build": "16H50",
|
||||
"version": "12.5.4"
|
||||
},
|
||||
{
|
||||
"build": "16H62",
|
||||
"version": "12.5.5"
|
||||
},
|
||||
{
|
||||
"build": "16H71",
|
||||
"version": "12.5.6"
|
||||
},
|
||||
{
|
||||
"build": "16H81",
|
||||
"version": "12.5.7"
|
||||
},
|
||||
{
|
||||
"build": "17A577",
|
||||
"version": "13.0"
|
||||
},
|
||||
{
|
||||
"build": "17A844",
|
||||
"version": "13.1"
|
||||
},
|
||||
{
|
||||
"build": "17A854",
|
||||
"version": "13.1.1"
|
||||
},
|
||||
{
|
||||
"build": "17A860",
|
||||
"version": "13.1.2"
|
||||
},
|
||||
{
|
||||
"build": "17A861",
|
||||
"version": "13.1.2"
|
||||
},
|
||||
{
|
||||
"build": "17A878",
|
||||
"version": "13.1.3"
|
||||
},
|
||||
{
|
||||
"build": "17B84",
|
||||
"version": "13.2"
|
||||
},
|
||||
{
|
||||
"build": "17B102",
|
||||
"version": "13.2.2"
|
||||
},
|
||||
{
|
||||
"build": "17B111",
|
||||
"version": "13.2.3"
|
||||
},
|
||||
{
|
||||
"build": "17C54",
|
||||
"version": "13.3"
|
||||
},
|
||||
{
|
||||
"build": "17D50",
|
||||
"version": "13.3.1"
|
||||
},
|
||||
{
|
||||
"build": "17E255",
|
||||
"version": "13.4"
|
||||
},
|
||||
{
|
||||
"build": "17E8255",
|
||||
"version": "13.4"
|
||||
},
|
||||
{
|
||||
"build": "17E262",
|
||||
"version": "13.4.1"
|
||||
},
|
||||
{
|
||||
"build": "17E8258",
|
||||
"version": "13.4.1"
|
||||
},
|
||||
{
|
||||
"build": "17F75",
|
||||
"version": "13.5"
|
||||
},
|
||||
{
|
||||
"build": "17F80",
|
||||
"version": "13.5.1"
|
||||
},
|
||||
{
|
||||
"build": "17G68",
|
||||
"version": "13.6"
|
||||
},
|
||||
{
|
||||
"build": "17G80",
|
||||
"version": "13.6.1"
|
||||
},
|
||||
{
|
||||
"build": "17H35",
|
||||
"version": "13.7"
|
||||
},
|
||||
{
|
||||
"build": "18A373",
|
||||
"version": "14.0"
|
||||
},
|
||||
{
|
||||
"build": "18A393",
|
||||
"version": "14.0.1"
|
||||
},
|
||||
{
|
||||
"build": "18A8395",
|
||||
"version": "14.1"
|
||||
},
|
||||
{
|
||||
"build": "18B92",
|
||||
"version": "14.2"
|
||||
},
|
||||
{
|
||||
"version": "14.2",
|
||||
"build": "18B111"
|
||||
},
|
||||
{
|
||||
"version": "14.2.1",
|
||||
"build": "18B121"
|
||||
},
|
||||
{
|
||||
"build": "18C66",
|
||||
"version": "14.3"
|
||||
},
|
||||
{
|
||||
"build": "18D52",
|
||||
"version": "14.4"
|
||||
},
|
||||
{
|
||||
"build": "18D61",
|
||||
"version": "14.4.1"
|
||||
},
|
||||
{
|
||||
"build": "18D70",
|
||||
"version": "14.4.2"
|
||||
},
|
||||
{
|
||||
"build": "18E199",
|
||||
"version": "14.5"
|
||||
},
|
||||
{
|
||||
"build": "18E212",
|
||||
"version": "14.5.1"
|
||||
},
|
||||
{
|
||||
"build": "18F72",
|
||||
"version": "14.6"
|
||||
},
|
||||
{
|
||||
"build": "18G69",
|
||||
"version": "14.7"
|
||||
},
|
||||
{
|
||||
"build": "18G82",
|
||||
"version": "14.7.1"
|
||||
},
|
||||
{
|
||||
"build": "18H17",
|
||||
"version": "14.8"
|
||||
},
|
||||
{
|
||||
"build": "18H107",
|
||||
"version": "14.8.1"
|
||||
},
|
||||
{
|
||||
"build": "19A341",
|
||||
"version": "15.0"
|
||||
},
|
||||
{
|
||||
"build": "19A346",
|
||||
"version": "15.0"
|
||||
},
|
||||
{
|
||||
"build": "19A348",
|
||||
"version": "15.0.1"
|
||||
},
|
||||
{
|
||||
"build": "19A404",
|
||||
"version": "15.0.2"
|
||||
},
|
||||
{
|
||||
"build": "19B74",
|
||||
"version": "15.1"
|
||||
},
|
||||
{
|
||||
"build": "19B81",
|
||||
"version": "15.1.1"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"build": "19F77",
|
||||
"version": "15.5"
|
||||
},
|
||||
{
|
||||
"build": "19G71",
|
||||
"version": "15.6"
|
||||
},
|
||||
{
|
||||
"build": "19G82",
|
||||
"version": "15.6.1"
|
||||
},
|
||||
{
|
||||
"build": "19H12",
|
||||
"version": "15.7"
|
||||
},
|
||||
{
|
||||
"build": "19H117",
|
||||
"version": "15.7.1"
|
||||
},
|
||||
{
|
||||
"build": "19H218",
|
||||
"version": "15.7.2"
|
||||
},
|
||||
{
|
||||
"version": "15.7.3",
|
||||
"build": "19H307"
|
||||
},
|
||||
{
|
||||
"version": "15.7.4",
|
||||
"build": "19H321"
|
||||
},
|
||||
{
|
||||
"version": "15.7.5",
|
||||
"build": "19H332"
|
||||
},
|
||||
{
|
||||
"version": "15.7.6",
|
||||
"build": "19H349"
|
||||
},
|
||||
{
|
||||
"build": "20A362",
|
||||
"version": "16.0"
|
||||
},
|
||||
{
|
||||
"build": "20A371",
|
||||
"version": "16.0.1"
|
||||
},
|
||||
{
|
||||
"build": "20A380",
|
||||
"version": "16.0.2"
|
||||
},
|
||||
{
|
||||
"build": "20A392",
|
||||
"version": "16.0.3"
|
||||
},
|
||||
{
|
||||
"build": "20B82",
|
||||
"version": "16.1"
|
||||
},
|
||||
{
|
||||
"build": "20B101",
|
||||
"version": "16.1.1"
|
||||
},
|
||||
{
|
||||
"build": "20B110",
|
||||
"version": "16.1.2"
|
||||
},
|
||||
{
|
||||
"build": "20C65",
|
||||
"version": "16.2"
|
||||
},
|
||||
{
|
||||
"build": "20D47",
|
||||
"version": "16.3"
|
||||
},
|
||||
{
|
||||
"build": "20D67",
|
||||
"version": "16.3.1"
|
||||
},
|
||||
{
|
||||
"build": "20E247",
|
||||
"version": "16.4"
|
||||
},
|
||||
{
|
||||
"build": "20E252",
|
||||
"version": "16.4.1"
|
||||
},
|
||||
{
|
||||
"version": "16.5",
|
||||
"beta": null,
|
||||
"build": "20F66"
|
||||
}
|
||||
]
|
||||
@@ -158,6 +158,7 @@ class Manifest(IOSExtraction):
|
||||
"mode": oct(self._get_key(file_metadata, "Mode")),
|
||||
"owner": self._get_key(file_metadata, "UserID"),
|
||||
"size": self._get_key(file_metadata, "Size"),
|
||||
"type": "file" if file_data["flags"] == 1 else "directory",
|
||||
})
|
||||
except Exception:
|
||||
self.log.exception("Error reading manifest file metadata for file with ID %s "
|
||||
|
||||
@@ -34,7 +34,6 @@ class IOSExtraction(MVTModule):
|
||||
|
||||
self.is_backup = False
|
||||
self.is_fs_dump = False
|
||||
self.is_sysdiagnose = False
|
||||
|
||||
def _recover_sqlite_db_if_needed(self, file_path: str,
|
||||
forced: Optional[bool] = False) -> None:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import plistlib
|
||||
import sqlite3
|
||||
@@ -55,18 +56,20 @@ class Analytics(IOSExtraction):
|
||||
if ioc:
|
||||
self.log.warning("Found mention of a malicious process \"%s\" in %s file at %s",
|
||||
value, result["artifact"],
|
||||
result["timestamp"])
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
result["isodate"])
|
||||
new_result = copy.copy(result)
|
||||
new_result["matched_indicator"] = ioc
|
||||
self.detected.append(new_result)
|
||||
continue
|
||||
|
||||
ioc = self.indicators.check_domain(value)
|
||||
if ioc:
|
||||
self.log.warning("Found mention of a malicious domain \"%s\" in %s file at %s",
|
||||
value, result["artifact"],
|
||||
result["timestamp"])
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
result["isodate"])
|
||||
new_result = copy.copy(result)
|
||||
new_result["matched_indicator"] = ioc
|
||||
self.detected.append(new_result)
|
||||
|
||||
def _extract_analytics_data(self):
|
||||
artifact = self.file_path.split("/")[-1]
|
||||
|
||||
@@ -15,8 +15,6 @@ from ..base import IOSExtraction
|
||||
class Filesystem(IOSExtraction):
|
||||
"""This module extracts creation and modification date of files from a
|
||||
full file-system dump.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -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/
|
||||
|
||||
from .applications import Applications
|
||||
from .calendar import Calendar
|
||||
from .calls import Calls
|
||||
from .chrome_favicon import ChromeFavicon
|
||||
from .chrome_history import ChromeHistory
|
||||
@@ -28,4 +30,5 @@ MIXED_MODULES = [Calls, ChromeFavicon, ChromeHistory, Contacts, FirefoxFavicon,
|
||||
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
||||
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
|
||||
TCC, SMS, SMSAttachments, WebkitResourceLoadStatistics,
|
||||
WebkitSessionResourceLog, Whatsapp, Shortcuts]
|
||||
WebkitSessionResourceLog, Whatsapp, Shortcuts, Applications,
|
||||
Calendar]
|
||||
|
||||
123
mvt/ios/modules/mixed/applications.py
Normal file
123
mvt/ios/modules/mixed/applications.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# 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
|
||||
import os
|
||||
import plistlib
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from mvt.common.module import DatabaseNotFoundError
|
||||
from mvt.common.utils import convert_datetime_to_iso
|
||||
from mvt.ios.modules.base import IOSExtraction
|
||||
|
||||
APPLICATIONS_DB_PATH = [
|
||||
"private/var/containers/Bundle/Application/*/iTunesMetadata.plist"
|
||||
]
|
||||
|
||||
|
||||
class Applications(IOSExtraction):
|
||||
"""Extract information from accounts installed on the phone."""
|
||||
def __init__(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
target_path: Optional[str] = None,
|
||||
results_path: Optional[str] = None,
|
||||
fast_mode: Optional[bool] = False,
|
||||
log: logging.Logger = logging.getLogger(__name__),
|
||||
results: Optional[list] = None
|
||||
) -> None:
|
||||
super().__init__(file_path=file_path, target_path=target_path,
|
||||
results_path=results_path, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
if "isodate" in record:
|
||||
return {
|
||||
"timestamp": record["isodate"],
|
||||
"module": self.__class__.__name__,
|
||||
"event": "app_installed",
|
||||
"data": f"App {record.get('name', '')} version {record.get('bundleShortVersionString', '')} from {record.get('artistName', '')} installed from {record.get('sourceApp', '')}"
|
||||
}
|
||||
return []
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
for result in self.results:
|
||||
if self.indicators:
|
||||
ioc = self.indicators.check_process(result["softwareVersionBundleId"])
|
||||
if ioc:
|
||||
self.log.warning("Malicious application %s identified", result["softwareVersionBundleId"])
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
ioc = self.indicators.check_app_id(result["softwareVersionBundleId"])
|
||||
if ioc:
|
||||
self.log.warning("Malicious application %s identified", result["softwareVersionBundleId"])
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if result.get("sourceApp", "com.apple.AppStore") not in ["com.apple.AppStore", "com.apple.dmd", "dmd"]:
|
||||
self.log.warning("Suspicious app not installed from the App Store or MDM: %s", result["softwareVersionBundleId"])
|
||||
self.detected.append(result)
|
||||
|
||||
def _parse_itunes_timestamp(self, entry: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Parse the iTunes metadata info
|
||||
"""
|
||||
if entry.get("com.apple.iTunesStore.downloadInfo", {}).get("purchaseDate", None):
|
||||
timestamp = datetime.strptime(
|
||||
entry["com.apple.iTunesStore.downloadInfo"]["purchaseDate"],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
timestamp_utc = timestamp.astimezone(timezone.utc)
|
||||
entry["isodate"] = convert_datetime_to_iso(timestamp_utc)
|
||||
|
||||
def _parse_itunes_metadata(self, plist_path: str) -> None:
|
||||
"""
|
||||
Parse iTunesMetadata.plist file from an application in fs dump
|
||||
"""
|
||||
with open(plist_path, "rb") as f:
|
||||
entry = plistlib.load(f)
|
||||
|
||||
entry["file_path"] = plist_path
|
||||
self._parse_itunes_timestamp(entry)
|
||||
self.results.append(entry)
|
||||
|
||||
def _parse_info_plist(self, plist_path: str) -> None:
|
||||
"""
|
||||
Parse Info.plist file from backup
|
||||
"""
|
||||
with open(plist_path, "rb") as f:
|
||||
data = plistlib.load(f)
|
||||
|
||||
for app in data.get("Applications", {}):
|
||||
app_data = data["Applications"][app]
|
||||
entry = {"name": app}
|
||||
metadata = plistlib.loads(app_data["iTunesMetadata"])
|
||||
entry.update(metadata)
|
||||
|
||||
self._parse_itunes_timestamp(entry)
|
||||
|
||||
if "PlaceholderIcon" in app_data:
|
||||
sha256_hash = hashlib.sha256()
|
||||
sha256_hash.update(app_data["PlaceholderIcon"])
|
||||
entry["icon_sha256"] = sha256_hash.hexdigest()
|
||||
|
||||
self.results.append(entry)
|
||||
|
||||
def run(self) -> None:
|
||||
if self.is_backup:
|
||||
plist_path = os.path.join(self.target_path, "Info.plist")
|
||||
if not os.path.isfile(plist_path):
|
||||
raise DatabaseNotFoundError("Impossible to find Info.plist file")
|
||||
self._parse_info_plist(plist_path)
|
||||
elif self.is_fs_dump:
|
||||
for file_path in self._get_fs_files_from_patterns(APPLICATIONS_DB_PATH):
|
||||
self._parse_itunes_metadata(file_path)
|
||||
|
||||
self.log.info("Extracted a total of %d applications",
|
||||
len(self.results))
|
||||
136
mvt/ios/modules/mixed/calendar.py
Normal file
136
mvt/ios/modules/mixed/calendar.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# 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 sqlite3
|
||||
from typing import Optional, Union
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_iso
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CALENDAR_BACKUP_IDS = [
|
||||
"2041457d5fe04d39d0ab481178355df6781e6858",
|
||||
]
|
||||
CALENDAR_ROOT_PATHS = [
|
||||
"private/var/mobile/Library/Calendar/Calendar.sqlitedb"
|
||||
]
|
||||
|
||||
|
||||
class Calendar(IOSExtraction):
|
||||
"""This module extracts all calendar entries."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
target_path: Optional[str] = None,
|
||||
results_path: Optional[str] = None,
|
||||
fast_mode: Optional[bool] = False,
|
||||
log: logging.Logger = logging.getLogger(__name__),
|
||||
results: Optional[list] = None
|
||||
) -> None:
|
||||
super().__init__(file_path=file_path, target_path=target_path,
|
||||
results_path=results_path, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
self.timestamps = [
|
||||
"start_date",
|
||||
"end_date",
|
||||
"last_modified",
|
||||
"creation_date",
|
||||
"participant_last_modified"
|
||||
]
|
||||
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
records = []
|
||||
for timestamp in self.timestamps:
|
||||
if timestamp not in record or not record[timestamp]:
|
||||
continue
|
||||
|
||||
records.append({
|
||||
"timestamp": record[timestamp],
|
||||
"module": self.__class__.__name__,
|
||||
"event": timestamp,
|
||||
"data": f"Calendar event {record['summary']} ({record['description']}) "
|
||||
f"(invitation by {record['participant_email']})"
|
||||
})
|
||||
return records
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
for result in self.results:
|
||||
if result["participant_email"] and self.indicators:
|
||||
ioc = self.indicators.check_email(result["participant_email"])
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
# Custom check for Quadream exploit
|
||||
if result["summary"] == "Meeting" and result["description"] == "Notes":
|
||||
self.log.warning("Potential Quadream exploit event identified: %s", result["uuid"])
|
||||
self.detected.append(result)
|
||||
|
||||
def _parse_calendar_db(self):
|
||||
"""
|
||||
Parse the calendar database
|
||||
"""
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT
|
||||
CalendarItem.ROWID as "id",
|
||||
CalendarItem.summary as "summary",
|
||||
CalendarItem.description as "description",
|
||||
CalendarItem.start_date as "start_date",
|
||||
CalendarItem.end_date as "end_date",
|
||||
CalendarItem.all_day as "all_day",
|
||||
CalendarItem.calendar_id as "calendar_id",
|
||||
CalendarItem.organizer_id as "organizer_id",
|
||||
CalendarItem.url as "url",
|
||||
CalendarItem.last_modified as "last_modified",
|
||||
CalendarItem.external_id as "external_id",
|
||||
CalendarItem.external_mod_tag as "external_mod_tag",
|
||||
CalendarItem.unique_identifier as "unique_identifier",
|
||||
CalendarItem.hidden as "hidden",
|
||||
CalendarItem.UUID as "uuid",
|
||||
CalendarItem.creation_date as "creation_date",
|
||||
CalendarItem.action as "action",
|
||||
CalendarItem.created_by_id as "created_by_id",
|
||||
Participant.UUID as "participant_uuid",
|
||||
Participant.email as "participant_email",
|
||||
Participant.phone_number as "participant_phone",
|
||||
Participant.comment as "participant_comment",
|
||||
Participant.last_modified as "participant_last_modified"
|
||||
FROM CalendarItem
|
||||
LEFT JOIN Participant ON Participant.ROWID = CalendarItem.organizer_id;
|
||||
""")
|
||||
|
||||
names = [description[0] for description in cur.description]
|
||||
for item in cur:
|
||||
entry = {}
|
||||
for index, value in enumerate(item):
|
||||
if names[index] in self.timestamps:
|
||||
if value is None or isinstance(value, str):
|
||||
entry[names[index]] = value
|
||||
else:
|
||||
entry[names[index]] = convert_mactime_to_iso(value)
|
||||
else:
|
||||
entry[names[index]] = value
|
||||
|
||||
self.results.append(entry)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
def run(self) -> None:
|
||||
self._find_ios_database(backup_ids=CALENDAR_BACKUP_IDS,
|
||||
root_paths=CALENDAR_ROOT_PATHS)
|
||||
self.log.info("Found calendar database at path: %s",
|
||||
self.file_path)
|
||||
|
||||
self._parse_calendar_db()
|
||||
|
||||
self.log.info("Extracted a total of %d calendar items",
|
||||
len(self.results))
|
||||
@@ -17,6 +17,200 @@ INTERACTIONC_BACKUP_IDS = [
|
||||
INTERACTIONC_ROOT_PATHS = [
|
||||
"private/var/mobile/Library/CoreDuet/People/interactionC.db",
|
||||
]
|
||||
# Taken from APOLLO
|
||||
# https://github.com/mac4n6/APOLLO/blob/master/modules/interaction_contact_interactions.txt
|
||||
QUERIES = [
|
||||
"""SELECT
|
||||
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
||||
ZINTERACTIONS.ZENDDATE AS "end_date",
|
||||
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
||||
ZINTERACTIONS.ZACCOUNT AS "account",
|
||||
ZINTERACTIONS.ZTARGETBUNDLEID AS "target_bundle_id",
|
||||
CASE ZINTERACTIONS.ZDIRECTION
|
||||
WHEN '0' THEN 'INCOMING'
|
||||
WHEN '1' THEN 'OUTGOING'
|
||||
END 'DIRECTION' AS "direction",
|
||||
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
||||
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
||||
ZCONTACTS.ZPERSONID AS "sender_personid",
|
||||
RECEIPIENTCONACT.ZDISPLAYNAME AS "recipient_display_name",
|
||||
RECEIPIENTCONACT.ZIDENTIFIER AS "recipient_identifier",
|
||||
RECEIPIENTCONACT.ZPERSONID AS "recipient_personid",
|
||||
ZINTERACTIONS.ZRECIPIENTCOUNT AS "recipient_count",
|
||||
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
||||
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
||||
ZATTACHMENT.ZCONTENTTEXT AS "content",
|
||||
ZATTACHMENT.ZUTI AS "uti",
|
||||
ZATTACHMENT.ZCONTENTURL AS "attachment_content_url",
|
||||
ZATTACHMENT.ZSIZEINBYTES AS "size",
|
||||
ZATTACHMENT.ZPHOTOLOCALIDENTIFIER AS "photo_local_id",
|
||||
HEX(ZATTACHMENT.ZIDENTIFIER) AS "attachment_id",
|
||||
ZATTACHMENT.ZCLOUDIDENTIFIER AS "cloud_id",
|
||||
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
||||
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
||||
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
||||
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
||||
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
||||
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
||||
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
||||
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE AS "last_incoming_recipient_date",
|
||||
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
||||
ZCONTACTS.ZCUSTOMIDENTIFIER AS "custom_id",
|
||||
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
||||
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
||||
ZINTERACTIONS.ZGROUPNAME AS "group_name",
|
||||
ZINTERACTIONS.ZDERIVEDINTENTIDENTIFIER AS "derivied_intent_id",
|
||||
ZINTERACTIONS.Z_PK AS "table_id"
|
||||
FROM ZINTERACTIONS
|
||||
LEFT JOIN ZCONTACTS
|
||||
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
LEFT JOIN Z_1INTERACTIONS
|
||||
ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
||||
LEFT JOIN ZATTACHMENT
|
||||
ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
||||
LEFT JOIN Z_2INTERACTIONRECIPIENT
|
||||
ON ZINTERACTIONS.Z_PK == Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
||||
LEFT JOIN ZCONTACTS RECEIPIENTCONACT
|
||||
ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS == RECEIPIENTCONACT.Z_PK;
|
||||
""",
|
||||
""" SELECT
|
||||
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
||||
ZINTERACTIONS.ZENDDATE AS "end_date",
|
||||
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
||||
ZINTERACTIONS.ZACCOUNT AS "account",
|
||||
ZINTERACTIONS.ZTARGETBUNDLEID AS "target_bundle_id",
|
||||
CASE ZINTERACTIONS.ZDIRECTION
|
||||
WHEN '0' THEN 'INCOMING'
|
||||
WHEN '1' THEN 'OUTGOING'
|
||||
END 'DIRECTION' AS "direction",
|
||||
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
||||
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
||||
ZCONTACTS.ZPERSONID AS "sender_personid",
|
||||
RECEIPIENTCONACT.ZDISPLAYNAME AS "recipient_display_name",
|
||||
RECEIPIENTCONACT.ZIDENTIFIER AS "recipient_identifier",
|
||||
RECEIPIENTCONACT.ZPERSONID AS "recipient_personid",
|
||||
ZINTERACTIONS.ZRECIPIENTCOUNT AS "recipient_count",
|
||||
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
||||
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
||||
ZATTACHMENT.ZCONTENTTEXT AS "content",
|
||||
ZATTACHMENT.ZUTI AS "uti",
|
||||
ZATTACHMENT.ZCONTENTURL AS "attachment_content_url",
|
||||
ZATTACHMENT.ZSIZEINBYTES AS "size",
|
||||
HEX(ZATTACHMENT.ZIDENTIFIER) AS "attachment_id",
|
||||
ZATTACHMENT.ZCLOUDIDENTIFIER AS "cloud_id",
|
||||
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
||||
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
||||
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
||||
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
||||
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
||||
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
||||
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
||||
CASE ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
||||
WHEN '0' THEN '0'
|
||||
ELSE ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
||||
END 'LAST INCOMING RECIPIENT DATE' AS "last_incoming_recipient_date",
|
||||
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
||||
ZCONTACTS.ZCUSTOMIDENTIFIER AS "custom_id",
|
||||
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
||||
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
||||
ZINTERACTIONS.Z_PK AS "table_id"
|
||||
FROM
|
||||
ZINTERACTIONS
|
||||
LEFT JOIN
|
||||
ZCONTACTS
|
||||
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
LEFT JOIN Z_1INTERACTIONS ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
||||
LEFT JOIN ZATTACHMENT ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
||||
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
||||
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK
|
||||
""",
|
||||
""" SELECT
|
||||
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
||||
ZINTERACTIONS.ZENDDATE AS "end_date",
|
||||
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
||||
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
||||
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
||||
ZCONTACTS.ZPERSONID AS "sender_personid",
|
||||
ZINTERACTIONS.ZDIRECTION AS "direction",
|
||||
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
||||
ZINTERACTIONS.ZMECHANISM AS "mechanism",
|
||||
ZINTERACTIONS.ZRECIPIENTCOUNT AS "recipient_count",
|
||||
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
||||
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
||||
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
||||
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
||||
CASE
|
||||
ZLASTINCOMINGRECIPIENTDATE
|
||||
WHEN
|
||||
'0'
|
||||
THEN
|
||||
'0'
|
||||
ELSE
|
||||
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
||||
END AS "last_incoming_recipient_date",
|
||||
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
||||
ZINTERACTIONS.ZACCOUNT AS 'account',
|
||||
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
||||
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
||||
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
||||
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
||||
ZCONTACTS.ZCUSTOMIDENTIFIER AS "custom_id",
|
||||
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
||||
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
||||
ZINTERACTIONS.Z_PK AS "table_id"
|
||||
FROM
|
||||
ZINTERACTIONS
|
||||
LEFT JOIN
|
||||
ZCONTACTS
|
||||
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
""",
|
||||
""" SELECT
|
||||
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
||||
ZINTERACTIONS.ZENDDATE AS "end_date",
|
||||
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
||||
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
||||
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
||||
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
||||
ZCONTACTS.ZPERSONID AS "sender_personid",
|
||||
ZINTERACTIONS.ZDIRECTION AS "direction",
|
||||
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
||||
ZINTERACTIONS.ZMECHANISM AS "mechanism",
|
||||
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
||||
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
||||
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
||||
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
||||
CASE
|
||||
ZLASTINCOMINGRECIPIENTDATE
|
||||
WHEN
|
||||
'0'
|
||||
THEN
|
||||
'0'
|
||||
ELSE
|
||||
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
||||
END AS "last_incoming_recipient_date",
|
||||
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
||||
ZINTERACTIONS.ZACCOUNT AS "account",
|
||||
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
||||
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
||||
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
||||
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
||||
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
||||
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
||||
ZINTERACTIONS.Z_PK AS "table_id"
|
||||
FROM
|
||||
ZINTERACTIONS
|
||||
LEFT JOIN
|
||||
ZCONTACTS
|
||||
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
"""
|
||||
]
|
||||
|
||||
|
||||
class InteractionC(IOSExtraction):
|
||||
@@ -66,8 +260,8 @@ class InteractionC(IOSExtraction):
|
||||
"event": timestamp,
|
||||
"data": f"[{record['bundle_id']}] {record['account']} - "
|
||||
f"from {record['sender_display_name']} ({record['sender_identifier']}) "
|
||||
f"to {record['recipient_display_name']} ({record['recipient_identifier']}):"
|
||||
f" {record['content']}"
|
||||
f"to {record.get('recipient_display_name', '')} ({record.get('recipient_identifier', '')}):"
|
||||
f" {record.get('content', '')}"
|
||||
})
|
||||
processed.append(record[timestamp])
|
||||
|
||||
@@ -81,108 +275,30 @@ class InteractionC(IOSExtraction):
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
# TODO: Support all versions.
|
||||
# Taken from:
|
||||
# https://github.com/mac4n6/APOLLO/blob/master/modules/interaction_contact_interactions.txt
|
||||
cur.execute("""
|
||||
SELECT
|
||||
ZINTERACTIONS.ZSTARTDATE,
|
||||
ZINTERACTIONS.ZENDDATE,
|
||||
ZINTERACTIONS.ZBUNDLEID,
|
||||
ZINTERACTIONS.ZACCOUNT,
|
||||
ZINTERACTIONS.ZTARGETBUNDLEID,
|
||||
CASE ZINTERACTIONS.ZDIRECTION
|
||||
WHEN '0' THEN 'INCOMING'
|
||||
WHEN '1' THEN 'OUTGOING'
|
||||
END 'DIRECTION',
|
||||
ZCONTACTS.ZDISPLAYNAME,
|
||||
ZCONTACTS.ZIDENTIFIER,
|
||||
ZCONTACTS.ZPERSONID,
|
||||
RECEIPIENTCONACT.ZDISPLAYNAME,
|
||||
RECEIPIENTCONACT.ZIDENTIFIER,
|
||||
RECEIPIENTCONACT.ZPERSONID,
|
||||
ZINTERACTIONS.ZRECIPIENTCOUNT,
|
||||
ZINTERACTIONS.ZDOMAINIDENTIFIER,
|
||||
ZINTERACTIONS.ZISRESPONSE,
|
||||
ZATTACHMENT.ZCONTENTTEXT,
|
||||
ZATTACHMENT.ZUTI,
|
||||
ZATTACHMENT.ZCONTENTURL,
|
||||
ZATTACHMENT.ZSIZEINBYTES,
|
||||
ZATTACHMENT.ZPHOTOLOCALIDENTIFIER,
|
||||
HEX(ZATTACHMENT.ZIDENTIFIER),
|
||||
ZATTACHMENT.ZCLOUDIDENTIFIER,
|
||||
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT,
|
||||
ZCONTACTS.ZINCOMINGSENDERCOUNT,
|
||||
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT,
|
||||
ZINTERACTIONS.ZCREATIONDATE,
|
||||
ZCONTACTS.ZCREATIONDATE,
|
||||
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE,
|
||||
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE,
|
||||
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE,
|
||||
ZCONTACTS.ZLASTINCOMINGSENDERDATE,
|
||||
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE,
|
||||
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE,
|
||||
ZCONTACTS.ZCUSTOMIDENTIFIER,
|
||||
ZINTERACTIONS.ZCONTENTURL,
|
||||
ZINTERACTIONS.ZLOCATIONUUID,
|
||||
ZINTERACTIONS.ZGROUPNAME,
|
||||
ZINTERACTIONS.ZDERIVEDINTENTIDENTIFIER,
|
||||
ZINTERACTIONS.Z_PK
|
||||
FROM ZINTERACTIONS
|
||||
LEFT JOIN ZCONTACTS
|
||||
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
LEFT JOIN Z_1INTERACTIONS
|
||||
ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
||||
LEFT JOIN ZATTACHMENT
|
||||
ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
||||
LEFT JOIN Z_2INTERACTIONRECIPIENT
|
||||
ON ZINTERACTIONS.Z_PK == Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
||||
LEFT JOIN ZCONTACTS RECEIPIENTCONACT
|
||||
ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS == RECEIPIENTCONACT.Z_PK;
|
||||
""")
|
||||
# names = [description[0] for description in cur.description]
|
||||
try:
|
||||
cur.execute(QUERIES[0])
|
||||
except sqlite3.OperationalError:
|
||||
try:
|
||||
cur.execute(QUERIES[1])
|
||||
except sqlite3.OperationalError:
|
||||
try:
|
||||
cur.execute(QUERIES[2])
|
||||
except sqlite3.OperationalError:
|
||||
cur.execute(QUERIES[3])
|
||||
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"start_date": convert_mactime_to_iso(row[0]),
|
||||
"end_date": convert_mactime_to_iso(row[1]),
|
||||
"bundle_id": row[2],
|
||||
"account": row[3],
|
||||
"target_bundle_id": row[4],
|
||||
"direction": row[5],
|
||||
"sender_display_name": row[6],
|
||||
"sender_identifier": row[7],
|
||||
"sender_personid": row[8],
|
||||
"recipient_display_name": row[9],
|
||||
"recipient_identifier": row[10],
|
||||
"recipient_personid": row[11],
|
||||
"recipient_count": row[12],
|
||||
"domain_identifier": row[13],
|
||||
"is_response": row[14],
|
||||
"content": row[15],
|
||||
"uti": row[16],
|
||||
"content_url": row[17],
|
||||
"size": row[18],
|
||||
"photo_local_id": row[19],
|
||||
"attachment_id": row[20],
|
||||
"cloud_id": row[21],
|
||||
"incoming_recipient_count": row[22],
|
||||
"incoming_sender_count": row[23],
|
||||
"outgoing_recipient_count": row[24],
|
||||
"interactions_creation_date": convert_mactime_to_iso(row[25]) if row[25] else None,
|
||||
"contacts_creation_date": convert_mactime_to_iso(row[26]) if row[26] else None,
|
||||
"first_incoming_recipient_date": convert_mactime_to_iso(row[27]) if row[27] else None,
|
||||
"first_incoming_sender_date": convert_mactime_to_iso(row[28]) if row[28] else None,
|
||||
"first_outgoing_recipient_date": convert_mactime_to_iso(row[29]) if row[29] else None,
|
||||
"last_incoming_sender_date": convert_mactime_to_iso(row[30]) if row[30] else None,
|
||||
"last_incoming_recipient_date": convert_mactime_to_iso(row[31]) if row[31] else None,
|
||||
"last_outgoing_recipient_date": convert_mactime_to_iso(row[32]) if row[32] else None,
|
||||
"custom_id": row[33],
|
||||
"location_uuid": row[35],
|
||||
"group_name": row[36],
|
||||
"derivied_intent_id": row[37],
|
||||
"table_id": row[38]
|
||||
})
|
||||
names = [description[0] for description in cur.description]
|
||||
for item in cur:
|
||||
entry = {}
|
||||
for index, value in enumerate(item):
|
||||
if names[index] in self.timestamps:
|
||||
if value is None or isinstance(value, str):
|
||||
entry[names[index]] = value
|
||||
else:
|
||||
entry[names[index]] = convert_mactime_to_iso(value)
|
||||
else:
|
||||
entry[names[index]] = value
|
||||
|
||||
self.results.append(entry)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
@@ -51,7 +51,10 @@ class SMS(IOSExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
message_links = check_for_links(result.get("text", ""))
|
||||
message_links = result.get("links", [])
|
||||
# Making sure not link was ignored
|
||||
if message_links == []:
|
||||
message_links = check_for_links(result.get("text", ""))
|
||||
ioc = self.indicators.check_domains(message_links)
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
@@ -118,18 +121,15 @@ class SMS(IOSExtraction):
|
||||
if message.get("text", "").startswith(alert):
|
||||
self.log.warning("Apple warning about state-sponsored attack received on the %s",
|
||||
message["isodate"])
|
||||
self.results.append(message)
|
||||
else:
|
||||
# Extract links from the SMS message.
|
||||
message_links = check_for_links(message.get("text", ""))
|
||||
message["links"] = message_links
|
||||
|
||||
# If we find links in the messages or if they are empty we add
|
||||
# them to the list.
|
||||
if message_links or message.get("text", "").strip() == "":
|
||||
self.results.append(message)
|
||||
self.results.append(message)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d SMS messages containing links",
|
||||
self.log.info("Extracted a total of %d SMS messages",
|
||||
len(self.results))
|
||||
|
||||
@@ -70,7 +70,15 @@ class WebkitResourceLoadStatistics(IOSExtraction):
|
||||
cur = conn.cursor()
|
||||
|
||||
try:
|
||||
cur.execute("SELECT * from ObservedDomains;")
|
||||
# FIXME: table contains extra fields with timestamp here
|
||||
cur.execute("""
|
||||
SELECT
|
||||
domainID,
|
||||
registrableDomain,
|
||||
lastSeen,
|
||||
hadUserInteraction
|
||||
from ObservedDomains;
|
||||
""")
|
||||
except sqlite3.OperationalError:
|
||||
return
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class Whatsapp(IOSExtraction):
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
text = record.get("ZTEXT", "").replace("\n", "\\n")
|
||||
links_text = ""
|
||||
if record["links"]:
|
||||
if record.get("links"):
|
||||
links_text = " - Embedded links: " + ", ".join(record["links"])
|
||||
|
||||
return {
|
||||
@@ -112,14 +112,13 @@ class Whatsapp(IOSExtraction):
|
||||
or link.startswith("https://mmg.whatsapp.net/")):
|
||||
filtered_links.append(link)
|
||||
|
||||
# If we find messages with links, or if there's an empty message
|
||||
# we add it to the results list.
|
||||
# Add all the links found to the record
|
||||
if filtered_links or (message.get("ZTEXT") or "").strip() == "":
|
||||
message["links"] = list(set(filtered_links))
|
||||
self.results.append(message)
|
||||
self.results.append(message)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d WhatsApp messages containing links",
|
||||
self.log.info("Extracted a total of %d WhatsApp messages",
|
||||
len(self.results))
|
||||
|
||||
3
mvt/ios/modules/post_analysis/__init__.py
Normal file
3
mvt/ios/modules/post_analysis/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .attachment_deletion import PostAttachmentDeletion
|
||||
|
||||
POST_ANALYSIS_MODULES = [PostAttachmentDeletion]
|
||||
101
mvt/ios/modules/post_analysis/attachment_deletion.py
Normal file
101
mvt/ios/modules/post_analysis/attachment_deletion.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import logging
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from mvt.common.module import PostAnalysisModule
|
||||
|
||||
|
||||
class PostAttachmentDeletion(PostAnalysisModule):
|
||||
"""
|
||||
Heuristic detection for attachment deletion in a cert time period.
|
||||
|
||||
|
||||
This module implements a hueuristic detection for a multiple iOS SMS attachmemt being deleted
|
||||
in a short period of time. This is a similar concept to the following script used
|
||||
by Kaspersky Labs to detect infections with the Triangulation iOS malware:
|
||||
https://github.com/KasperskyLab/triangle_check/blob/main/triangle_check/__init__.py
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
target_path: Optional[str] = None,
|
||||
results_path: Optional[str] = None,
|
||||
fast_mode: Optional[bool] = False,
|
||||
log: logging.Logger = logging.getLogger(__name__),
|
||||
results: Optional[list] = None
|
||||
) -> None:
|
||||
super().__init__(file_path=file_path, target_path=target_path,
|
||||
results_path=results_path, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
|
||||
self.required_modules = ["manifest"]
|
||||
|
||||
|
||||
def load_locationd_events(self):
|
||||
locationd_clients = self.results["locationd_clients"]
|
||||
locations_stopped_event = [event for event in locationd_clients if "LocationTimeStopped" in event]
|
||||
return locations_stopped_event
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Run the post-processing module.
|
||||
|
||||
The logical is to look for all SMS attachment directories which were recently created
|
||||
shortly before their last modified time, but which have no contained files.
|
||||
"""
|
||||
for module in self.required_modules:
|
||||
if module not in self.results:
|
||||
raise Exception(f"Required module {module} was not found in results. Did you run the required modules?")
|
||||
|
||||
locationd_events = []
|
||||
locationd_client_iocs = [
|
||||
"com.apple.locationd.bundle-/System/Library/LocationBundles/IonosphereHarvest.bundle",
|
||||
"com.apple.locationd.bundle-/System/Library/LocationBundles/WRMLinkSelection.bundle"
|
||||
]
|
||||
for event in self.load_locationd_events():
|
||||
for ioc in locationd_client_iocs:
|
||||
if ioc in event["Registered"]:
|
||||
locationd_events.append(event)
|
||||
print(event)
|
||||
|
||||
|
||||
|
||||
# Filter the relevant events from the manifest:
|
||||
events_by_time = {}
|
||||
sms_files = [event for event in self.results["manifest"] if event["relative_path"].startswith("Library/SMS/Attachments/")]
|
||||
attachment_folders = {}
|
||||
for record in sorted(sms_files, key=lambda x: x["relative_path"]):
|
||||
num_path_segments = record["relative_path"].count('/')
|
||||
# Skip entries with a full-path
|
||||
# if not (num_path_segments == 3 or num_path_segments == 4):
|
||||
# continue
|
||||
|
||||
attachment_root = "/".join(record["relative_path"].split('/', 5)[:5])
|
||||
attachment_folder = attachment_folders.get(attachment_root, [])
|
||||
attachment_folder.append(record)
|
||||
attachment_folders[attachment_root] = attachment_folder
|
||||
|
||||
# Look for directories containing no files, which had a short lifespan
|
||||
for key, items in attachment_folders.items():
|
||||
has_files = any([item["flags"] == 1 for item in items])
|
||||
if has_files:
|
||||
continue
|
||||
|
||||
for item in sorted(items, key=lambda x: x["created"]):
|
||||
# item_created = datetime.datetime.strptime(item["created"], "%Y-%m-%d %H:%M:%S.%f")
|
||||
item_modified = datetime.datetime.strptime(item["modified"], "%Y-%m-%d %H:%M:%S.%f") # M
|
||||
status_changed = datetime.datetime.strptime(item["status_changed"], "%Y-%m-%d %H:%M:%S.%f") # C
|
||||
|
||||
# self.append_timeline(fs_stat['LastModified'], ('M', relativePath))
|
||||
# self.append_timeline(fs_stat['LastStatusChange'], ('C', relativePath))
|
||||
# self.append_timeline(fs_stat['Birth'], ('B', relativePath))
|
||||
|
||||
|
||||
# Skip items which were created and modified at the same time, likely never had files.
|
||||
# print(item["relative_path"], status_changed, item_modified)
|
||||
if item_modified == status_changed:
|
||||
print("changed == modified", item["relative_path"], status_changed, item_modified)
|
||||
continue
|
||||
|
||||
if (item_modified - status_changed): # < datetime.timedelta(minutes=10):
|
||||
self.log.info(f"Possible attachment deletion. Attachment folder '{key}' with no files, created and modified within 10 minutes. '{item['relative_path']}' created {item_created}, modified {item_modified})")
|
||||
@@ -2,275 +2,16 @@
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
from typing import Dict
|
||||
import json
|
||||
from logging import Logger
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
import packaging
|
||||
import pkgutil
|
||||
|
||||
IPHONE_MODELS = [
|
||||
{"identifier": "iPhone4,1", "description": "iPhone 4S"},
|
||||
{"identifier": "iPhone5,1", "description": "iPhone 5"},
|
||||
{"identifier": "iPhone5,2", "description": "iPhone 5"},
|
||||
{"identifier": "iPhone5,3", "description": "iPhone 5c"},
|
||||
{"identifier": "iPhone5,4", "description": "iPhone 5c"},
|
||||
{"identifier": "iPhone6,1", "description": "iPhone 5s"},
|
||||
{"identifier": "iPhone6,2", "description": "iPhone 5s"},
|
||||
{"identifier": "iPhone7,1", "description": "iPhone 6 Plus"},
|
||||
{"identifier": "iPhone7,2", "description": "iPhone 6"},
|
||||
{"identifier": "iPhone8,1", "description": "iPhone 6s"},
|
||||
{"identifier": "iPhone8,2", "description": "iPhone 6s Plus"},
|
||||
{"identifier": "iPhone8,4", "description": "iPhone SE (1st generation)"},
|
||||
{"identifier": "iPhone9,1", "description": "iPhone 7"},
|
||||
{"identifier": "iPhone9,2", "description": "iPhone 7 Plus"},
|
||||
{"identifier": "iPhone9,3", "description": "iPhone 7"},
|
||||
{"identifier": "iPhone9,4", "description": "iPhone 7 Plus"},
|
||||
{"identifier": "iPhone10,1", "description": "iPhone 8"},
|
||||
{"identifier": "iPhone10,2", "description": "iPhone 8 Plus"},
|
||||
{"identifier": "iPhone10,3", "description": "iPhone X"},
|
||||
{"identifier": "iPhone10,4", "description": "iPhone 8"},
|
||||
{"identifier": "iPhone10,5", "description": "iPhone 8 Plus"},
|
||||
{"identifier": "iPhone10,6", "description": "iPhone X"},
|
||||
{"identifier": "iPhone11,2", "description": "iPhone XS"},
|
||||
{"identifier": "iPhone11,4", "description": "iPhone XS Max"},
|
||||
{"identifier": "iPhone11,6", "description": "iPhone XS Max"},
|
||||
{"identifier": "iPhone11,8", "description": "iPhone XR"},
|
||||
{"identifier": "iPhone12,1", "description": "iPhone 11"},
|
||||
{"identifier": "iPhone12,3", "description": "iPhone 11 Pro"},
|
||||
{"identifier": "iPhone12,5", "description": "iPhone 11 Pro Max"},
|
||||
{"identifier": "iPhone12,8", "description": "iPhone SE (2nd generation)"},
|
||||
{"identifier": "iPhone13,1", "description": "iPhone 12 mini"},
|
||||
{"identifier": "iPhone13,2", "description": "iPhone 12"},
|
||||
{"identifier": "iPhone13,3", "description": "iPhone 12 Pro"},
|
||||
{"identifier": "iPhone13,4", "description": "iPhone 12 Pro Max"},
|
||||
{"identifier": "iPhone14,4", "description": "iPhone 13 Mini"},
|
||||
{"identifier": "iPhone14,5", "description": "iPhone 13"},
|
||||
{"identifier": "iPhone14,2", "description": "iPhone 13 Pro"},
|
||||
{"identifier": "iPhone14,3", "description": "iPhone 13 Pro Max"},
|
||||
{"identifier": "iPhone14,8", "decription": "iPhone 14 Plus"},
|
||||
{"identifier": "iPhone15,2", "description": "iPhone 14 Pro"},
|
||||
{"identifier": "iPhone15,3", "description": "iPhone 14 Pro Max"}
|
||||
]
|
||||
|
||||
IPHONE_IOS_VERSIONS = [
|
||||
{"build": "1A543a", "version": "1.0"},
|
||||
{"build": "1C25", "version": "1.0.1"},
|
||||
{"build": "1C28", "version": "1.0.2"},
|
||||
{"build": "3A109a", "version": "1.1.1"},
|
||||
{"build": "3B48b", "version": "1.1.2"},
|
||||
{"build": "4A93", "version": "1.1.3"},
|
||||
{"build": "4A102", "version": "1.1.4"},
|
||||
{"build": "5A347", "version": "2.0"},
|
||||
{"build": "5B108", "version": "2.0.1"},
|
||||
{"build": "5C1", "version": "2.0.2"},
|
||||
{"build": "5F136", "version": "2.1"},
|
||||
{"build": "5G77", "version": "2.2"},
|
||||
{"build": "5H11", "version": "2.2.1"},
|
||||
{"build": "7A341", "version": "3.0"},
|
||||
{"build": "7A400", "version": "3.0.1"},
|
||||
{"build": "7C144", "version": "3.1"},
|
||||
{"build": "7D11", "version": "3.1.2"},
|
||||
{"build": "7E18", "version": "3.1.3"},
|
||||
{"build": "8A293", "version": "4.0"},
|
||||
{"build": "8A306", "version": "4.0.1"},
|
||||
{"build": "8B117", "version": "4.1"},
|
||||
{"build": "8C148", "version": "4.2"},
|
||||
{"build": "8C148a", "version": "4.2.1"},
|
||||
{"build": "8C148", "version": "4.2.1"},
|
||||
{"build": "8E600", "version": "4.2.10"},
|
||||
{"build": "8E401", "version": "4.2.8"},
|
||||
{"build": "8E501", "version": "4.2.9"},
|
||||
{"build": "8F190", "version": "4.3"},
|
||||
{"build": "8J2", "version": "4.3.3"},
|
||||
{"build": "8K2", "version": "4.3.4"},
|
||||
{"build": "8L1", "version": "4.3.5"},
|
||||
{"build": "9A334", "version": "5.0"},
|
||||
{"build": "9A405", "version": "5.0.1"},
|
||||
{"build": "9A406", "version": "5.0.1"},
|
||||
{"build": "9B176", "version": "5.1"},
|
||||
{"build": "9B179", "version": "5.1"},
|
||||
{"build": "9B206", "version": "5.1.1"},
|
||||
{"build": "9B208", "version": "5.1.1"},
|
||||
{"build": "10A403", "version": "6.0"},
|
||||
{"build": "10A405", "version": "6.0"},
|
||||
{"build": "10A523", "version": "6.0.1"},
|
||||
{"build": "10A525", "version": "6.0.1"},
|
||||
{"build": "10A551", "version": "6.0.2"},
|
||||
{"build": "10B141", "version": "6.1"},
|
||||
{"build": "10B144", "version": "6.1"},
|
||||
{"build": "10B142", "version": "6.1"},
|
||||
{"build": "10B143", "version": "6.1"},
|
||||
{"build": "10B145", "version": "6.1.1"},
|
||||
{"build": "10B146", "version": "6.1.2"},
|
||||
{"build": "10B329", "version": "6.1.3"},
|
||||
{"build": "10B350", "version": "6.1.4"},
|
||||
{"build": "10B500", "version": "6.1.6"},
|
||||
{"build": "11B511", "version": "7.0.3"},
|
||||
{"build": "11B554a", "version": "7.0.4"},
|
||||
{"build": "11B601", "version": "7.0.5"},
|
||||
{"build": "11B651", "version": "7.0.6"},
|
||||
{"build": "11D169", "version": "7.1"},
|
||||
{"build": "11D167", "version": "7.1"},
|
||||
{"build": "11D201", "version": "7.1.1"},
|
||||
{"build": "11D257", "version": "7.1.2"},
|
||||
{"build": "12A365", "version": "8.0"},
|
||||
{"build": "12A366", "version": "8.0"},
|
||||
{"build": "12A402", "version": "8.0.1"},
|
||||
{"build": "12A405", "version": "8.0.2"},
|
||||
{"build": "12B411", "version": "8.1"},
|
||||
{"build": "12B435", "version": "8.1.1"},
|
||||
{"build": "12B436", "version": "8.1.1"},
|
||||
{"build": "12B440", "version": "8.1.2"},
|
||||
{"build": "12B466", "version": "8.1.3"},
|
||||
{"build": "12D508", "version": "8.2"},
|
||||
{"build": "12F70", "version": "8.3"},
|
||||
{"build": "12H143", "version": "8.4"},
|
||||
{"build": "12H321", "version": "8.4.1"},
|
||||
{"build": "13A344", "version": "9.0"},
|
||||
{"build": "13A342", "version": "9.0"},
|
||||
{"build": "13A343", "version": "9.0"},
|
||||
{"build": "13A404", "version": "9.0.1"},
|
||||
{"build": "13A405", "version": "9.0.1"},
|
||||
{"build": "13A452", "version": "9.0.2"},
|
||||
{"build": "13B143", "version": "9.1"},
|
||||
{"build": "13C75", "version": "9.2"},
|
||||
{"build": "13D15", "version": "9.2.1"},
|
||||
{"build": "13D20", "version": "9.2.1"},
|
||||
{"build": "13E237", "version": "9.3"},
|
||||
{"build": "13E233", "version": "9.3"},
|
||||
{"build": "13E234", "version": "9.3"},
|
||||
{"build": "13E238", "version": "9.3.1"},
|
||||
{"build": "13F69", "version": "9.3.2"},
|
||||
{"build": "13G34", "version": "9.3.3"},
|
||||
{"build": "13G35", "version": "9.3.4"},
|
||||
{"build": "13G36", "version": "9.3.5"},
|
||||
{"build": "13G37", "version": "9.3.6"},
|
||||
{"build": "14A403", "version": "10.0.1"},
|
||||
{"build": "14A456", "version": "10.0.2"},
|
||||
{"build": "14A551", "version": "10.0.3"},
|
||||
{"build": "14B72", "version": "10.1"},
|
||||
{"build": "14B72c", "version": "10.1"},
|
||||
{"build": "14B150", "version": "10.1.1"},
|
||||
{"build": "14B100", "version": "10.1.1"},
|
||||
{"build": "14C92", "version": "10.2"},
|
||||
{"build": "14D27", "version": "10.2.1"},
|
||||
{"build": "14E277", "version": "10.3"},
|
||||
{"build": "14E304", "version": "10.3.1"},
|
||||
{"build": "14F89", "version": "10.3.2"},
|
||||
{"build": "14G60", "version": "10.3.3"},
|
||||
{"build": "14G61", "version": "10.3.4"},
|
||||
{"build": "15A372", "version": "11.0"},
|
||||
{"build": "15A402", "version": "11.0.1"},
|
||||
{"build": "15A421", "version": "11.0.2"},
|
||||
{"build": "15A432", "version": "11.0.3"},
|
||||
{"build": "15B93", "version": "11.1"},
|
||||
{"build": "15B150", "version": "11.1.1"},
|
||||
{"build": "15B202", "version": "11.1.2"},
|
||||
{"build": "15C114", "version": "11.2"},
|
||||
{"build": "15C153", "version": "11.2.1"},
|
||||
{"build": "15C202", "version": "11.2.2"},
|
||||
{"build": "15D60", "version": "11.2.5"},
|
||||
{"build": "15D100", "version": "11.2.6"},
|
||||
{"build": "15E216", "version": "11.3"},
|
||||
{"build": "15E302", "version": "11.3.1"},
|
||||
{"build": "15F79", "version": "11.4"},
|
||||
{"build": "15G77", "version": "11.4.1"},
|
||||
{"build": "16A366", "version": "12.0"},
|
||||
{"build": "16A367", "version": "12.0"},
|
||||
{"build": "16A404", "version": "12.0.1"},
|
||||
{"build": "16A405", "version": "12.0.1"},
|
||||
{"build": "16B92", "version": "12.1"},
|
||||
{"build": "16B94", "version": "12.1"},
|
||||
{"build": "16B93", "version": "12.1"},
|
||||
{"build": "16C50", "version": "12.1.1"},
|
||||
{"build": "16C104", "version": "12.1.2"},
|
||||
{"build": "16C101", "version": "12.1.2"},
|
||||
{"build": "16D39", "version": "12.1.3"},
|
||||
{"build": "16D40", "version": "12.1.3"},
|
||||
{"build": "16D57", "version": "12.1.4"},
|
||||
{"build": "16E227", "version": "12.2"},
|
||||
{"build": "16F156", "version": "12.3"},
|
||||
{"build": "16F203", "version": "12.3.1"},
|
||||
{"build": "16F250", "version": "12.3.2"},
|
||||
{"build": "16G77", "version": "12.4"},
|
||||
{"build": "16G102", "version": "12.4.1"},
|
||||
{"build": "16G114", "version": "12.4.2"},
|
||||
{"build": "16G130", "version": "12.4.3"},
|
||||
{"build": "16G140", "version": "12.4.4"},
|
||||
{"build": "16G161", "version": "12.4.5"},
|
||||
{"build": "16G183", "version": "12.4.6"},
|
||||
{"build": "16G192", "version": "12.4.7"},
|
||||
{"build": "16G201", "version": "12.4.8"},
|
||||
{"build": "16H5", "version": "12.4.9"},
|
||||
{"build": "16H20", "version": "12.5"},
|
||||
{"build": "16H22", "version": "12.5.1"},
|
||||
{"build": "16H30", "version": "12.5.2"},
|
||||
{"build": "16H41", "version": "12.5.3"},
|
||||
{"build": "16H50", "version": "12.5.4"},
|
||||
{"build": "16H62", "version": "12.5.5"},
|
||||
{"build": "16H71", "version": "12.5.6"},
|
||||
{"build": "16H81", "version": "12.5.7"},
|
||||
{"build": "17A577", "version": "13.0"},
|
||||
{"build": "17A844", "version": "13.1"},
|
||||
{"build": "17A854", "version": "13.1.1"},
|
||||
{"build": "17A860", "version": "13.1.2"},
|
||||
{"build": "17A861", "version": "13.1.2"},
|
||||
{"build": "17A878", "version": "13.1.3"},
|
||||
{"build": "17B84", "version": "13.2"},
|
||||
{"build": "17B102", "version": "13.2.2"},
|
||||
{"build": "17B111", "version": "13.2.3"},
|
||||
{"build": "17C54", "version": "13.3"},
|
||||
{"build": "17D50", "version": "13.3.1"},
|
||||
{"build": "17E255", "version": "13.4"},
|
||||
{"build": "17E8255", "version": "13.4"},
|
||||
{"build": "17E262", "version": "13.4.1"},
|
||||
{"build": "17E8258", "version": "13.4.1"},
|
||||
{"build": "17F75", "version": "13.5"},
|
||||
{"build": "17F80", "version": "13.5.1"},
|
||||
{"build": "17G68", "version": "13.6"},
|
||||
{"build": "17G80", "version": "13.6.1"},
|
||||
{"build": "17H35", "version": "13.7"},
|
||||
{"build": "18A373", "version": "14.0"},
|
||||
{"build": "18A393", "version": "14.0.1"},
|
||||
{"build": "18A8395", "version": "14.1"},
|
||||
{"build": "18B92", "version": "14.2"},
|
||||
{"build": "18C66", "version": "14.3"},
|
||||
{"build": "18D52", "version": "14.4"},
|
||||
{"build": "18D61", "version": "14.4.1"},
|
||||
{"build": "18D70", "version": "14.4.2"},
|
||||
{"build": "18E199", "version": "14.5"},
|
||||
{"build": "18E212", "version": "14.5.1"},
|
||||
{"build": "18F72", "version": "14.6"},
|
||||
{"build": "18G69", "version": "14.7"},
|
||||
{"build": "18G82", "version": "14.7.1"},
|
||||
{"build": "18H17", "version": "14.8"},
|
||||
{"build": "18H107", "version": "14.8.1"},
|
||||
{"build": "19A341", "version": "15.0"},
|
||||
{"build": "19A346", "version": "15.0"},
|
||||
{"build": "19A348", "version": "15.0.1"},
|
||||
{"build": "19A404", "version": "15.0.2"},
|
||||
{"build": "19B74", "version": "15.1"},
|
||||
{"build": "19B81", "version": "15.1.1"},
|
||||
{"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"},
|
||||
{"build": "19F77", "version": "15.5"},
|
||||
{"build": "19G71", "version": "15.6"},
|
||||
{"build": "19G82", "version": "15.6.1"},
|
||||
{"build": "19H12", "version": "15.7"},
|
||||
{"build": "19H117", "version": "15.7.1"},
|
||||
{"build": "19H218", "version": "15.7.2"},
|
||||
{"build": "20A362", "version": "16.0"},
|
||||
{"build": "20B82", "version": "16.1"},
|
||||
{"build": "20B101", "version": "16.1.1"},
|
||||
{"build": "20B110", "version": "16.1.2"},
|
||||
{"build": "20C65", "version": "16.2"},
|
||||
{"build": "20D47", "version": "16.3"},
|
||||
{"build": "20D67", "version": "16.3.1"}
|
||||
]
|
||||
IPHONE_MODELS = json.loads(pkgutil.get_data("mvt", "ios/data/ios_models.json"))
|
||||
IPHONE_IOS_VERSIONS = json.loads(pkgutil.get_data("mvt", "ios/data/ios_versions.json"))
|
||||
|
||||
|
||||
def get_device_desc_from_id(identifier: str,
|
||||
|
||||
6
ruff.toml
Normal file
6
ruff.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
||||
[per-file-ignores]
|
||||
"__init__.py" = ["F401"]
|
||||
@@ -42,6 +42,9 @@ console_scripts =
|
||||
mvt-ios = mvt.ios:cli
|
||||
mvt-android = mvt.android:cli
|
||||
|
||||
[options.package_data]
|
||||
mvt = ios/data/*.json
|
||||
|
||||
[flake8]
|
||||
max-complexity = 10
|
||||
max-line-length = 1000
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Mobile Verification Toolkit (MVT) - Private
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# This file is part of MVT Private and its content is confidential.
|
||||
# Please refer to the project maintainers before sharing with others.
|
||||
|
||||
import logging
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from mvt.android.modules.androidqf.dumpsys_accessibility import \
|
||||
DumpsysAccessibility
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Mobile Verification Toolkit (MVT) - Private
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# This file is part of MVT Private and its content is confidential.
|
||||
# Please refer to the project maintainers before sharing with others.
|
||||
|
||||
import logging
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from mvt.android.modules.androidqf.dumpsys_appops import DumpsysAppops
|
||||
from mvt.common.module import run_module
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Mobile Verification Toolkit (MVT) - Private
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# This file is part of MVT Private and its content is confidential.
|
||||
# Please refer to the project maintainers before sharing with others.
|
||||
# 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
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Mobile Verification Toolkit (MVT) - Private
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# This file is part of MVT Private and its content is confidential.
|
||||
# Please refer to the project maintainers before sharing with others.
|
||||
|
||||
import logging
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from mvt.android.modules.androidqf.dumpsys_receivers import DumpsysReceivers
|
||||
from mvt.common.module import run_module
|
||||
|
||||
@@ -7,6 +7,7 @@ import logging
|
||||
import os
|
||||
|
||||
from mvt.android.modules.androidqf.getprop import Getprop
|
||||
from mvt.common.indicators import Indicators
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_artifact_folder
|
||||
@@ -18,5 +19,18 @@ class TestAndroidqfGetpropAnalysis:
|
||||
m = Getprop(target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging)
|
||||
run_module(m)
|
||||
assert len(m.results) == 10
|
||||
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||
assert m.results[0]["value"] == "lz4"
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
|
||||
def test_androidqf_getprop_detection(self, indicator_file):
|
||||
m = Getprop(target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging)
|
||||
ind = Indicators(log=logging.getLogger())
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 10
|
||||
assert len(m.detected) == 1
|
||||
assert m.detected[0]["name"] == "dalvik.vm.heapmaxfree"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Mobile Verification Toolkit (MVT) - Private
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# This file is part of MVT Private and its content is confidential.
|
||||
# Please refer to the project maintainers before sharing with others.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from mvt.android.modules.androidqf.settings import Settings
|
||||
from mvt.common.module import run_module
|
||||
|
||||
0
tests/android_bugreport/__init__.py
Normal file
0
tests/android_bugreport/__init__.py
Normal file
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
|
||||
from mvt.android.modules.bugreport.appops import Appops
|
||||
from mvt.android.modules.bugreport.packages import Packages
|
||||
from mvt.android.modules.bugreport.getprop import Getprop
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_artifact_folder
|
||||
@@ -40,3 +41,7 @@ class TestBugreportAnalysis:
|
||||
assert m.results[1]["package_name"] == "com.instagram.android"
|
||||
assert len(m.results[0]["permissions"]) == 4
|
||||
assert len(m.results[1]["permissions"]) == 32
|
||||
|
||||
def test_getprop_module(self):
|
||||
m = self.launch_bug_report_module(Getprop)
|
||||
assert len(m.results) == 0
|
||||
@@ -16,6 +16,7 @@ def generate_test_stix_file(file_path):
|
||||
processes = ["Launch"]
|
||||
emails = ["foobar@example.org"]
|
||||
filenames = ["/var/foobar/txt"]
|
||||
android_property = ["sys.foobar"]
|
||||
|
||||
res = []
|
||||
malware = Malware(name="TestMalware", is_family=False, description="")
|
||||
@@ -40,6 +41,11 @@ def generate_test_stix_file(file_path):
|
||||
res.append(i)
|
||||
res.append(Relationship(i, "indicates", malware))
|
||||
|
||||
for p in android_property:
|
||||
i = Indicator(indicator_types=["malicious-activity"], pattern="[android-property:name='{}']".format(p), pattern_type="stix")
|
||||
res.append(i)
|
||||
res.append(Relationship(i, "indicates", malware))
|
||||
|
||||
bundle = Bundle(objects=res)
|
||||
with open(file_path, "w+", encoding="utf-8") as f:
|
||||
f.write(bundle.serialize(pretty=True))
|
||||
|
||||
Binary file not shown.
@@ -14,20 +14,29 @@ class TestIndicators:
|
||||
def test_parse_stix2(self, indicator_file):
|
||||
ind = Indicators(log=logging)
|
||||
ind.load_indicators_files([indicator_file], load_default=False)
|
||||
assert ind.ioc_collections[0]["count"] == 4
|
||||
assert ind.ioc_collections[0]["count"] == 5
|
||||
assert len(ind.ioc_collections[0]["domains"]) == 1
|
||||
assert len(ind.ioc_collections[0]["emails"]) == 1
|
||||
assert len(ind.ioc_collections[0]["file_names"]) == 1
|
||||
assert len(ind.ioc_collections[0]["processes"]) == 1
|
||||
assert len(ind.ioc_collections[0]["android_property_names"]) == 1
|
||||
|
||||
def test_check_domain(self, indicator_file):
|
||||
ind = Indicators(log=logging)
|
||||
ind.load_indicators_files([indicator_file], load_default=False)
|
||||
assert ind.check_domain(42) is None
|
||||
assert ind.check_domain("https://www.example.org/foobar")
|
||||
assert ind.check_domain("http://example.org:8080/toto")
|
||||
assert ind.check_domain("https://github.com") is None
|
||||
|
||||
def test_check_android_property(self, indicator_file):
|
||||
ind = Indicators(log=logging)
|
||||
ind.load_indicators_files([indicator_file], load_default=False)
|
||||
assert ind.check_android_property_name("sys.foobar")
|
||||
assert ind.check_android_property_name("sys.soundsokay") is None
|
||||
|
||||
def test_env_stix(self, indicator_file):
|
||||
os.environ["MVT_STIX2"] = indicator_file
|
||||
ind = Indicators(log=logging)
|
||||
ind.load_indicators_files([], load_default=False)
|
||||
assert ind.total_ioc_count == 4
|
||||
assert ind.total_ioc_count == 5
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
from ..utils import get_artifact_folder
|
||||
import os
|
||||
|
||||
from mvt.common.utils import (convert_datetime_to_iso, convert_mactime_to_iso,
|
||||
convert_unix_to_iso,
|
||||
convert_unix_to_utc_datetime,
|
||||
generate_hashes_from_path,
|
||||
get_sha256_from_file_path)
|
||||
|
||||
from ..utils import get_artifact_folder
|
||||
|
||||
TEST_DATE_EPOCH = 1626566400
|
||||
TEST_DATE_ISO = "2021-07-18 00:00:00.000000"
|
||||
TEST_DATE_MAC = TEST_DATE_EPOCH - 978307200
|
||||
|
||||
34
tests/ios_backup/test_calendar.py
Normal file
34
tests/ios_backup/test_calendar.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||
# 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.calendar import Calendar
|
||||
|
||||
from ..utils import get_ios_backup_folder
|
||||
|
||||
|
||||
class TestCalendarModule:
|
||||
|
||||
def test_calendar(self):
|
||||
m = Calendar(target_path=get_ios_backup_folder())
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.detected) == 0
|
||||
assert m.results[0]["summary"] == "Super interesting meeting"
|
||||
|
||||
def test_calendar_detection(self, indicator_file):
|
||||
m = Calendar(target_path=get_ios_backup_folder())
|
||||
ind = Indicators(log=logging.getLogger())
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["emails"].append("user@example.org")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.detected) == 1
|
||||
@@ -3,9 +3,6 @@
|
||||
# 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.webkit_resource_load_statistics import \
|
||||
WebkitResourceLoadStatistics
|
||||
|
||||
@@ -17,8 +17,8 @@ class TestFilesystem:
|
||||
def test_filesystem(self):
|
||||
m = Filesystem(target_path=get_ios_backup_folder())
|
||||
run_module(m)
|
||||
assert len(m.results) == 12
|
||||
assert len(m.timeline) == 12
|
||||
assert len(m.results) == 14
|
||||
assert len(m.timeline) == 14
|
||||
assert len(m.detected) == 0
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
@@ -29,6 +29,6 @@ class TestFilesystem:
|
||||
ind.ioc_collections[0]["processes"].append("64d0019cb3d46bfc8cce545a8ba54b93e7ea9347")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 12
|
||||
assert len(m.timeline) == 12
|
||||
assert len(m.results) == 14
|
||||
assert len(m.timeline) == 14
|
||||
assert len(m.detected) == 1
|
||||
|
||||
Reference in New Issue
Block a user