1
mirror of https://github.com/mvt-project/mvt synced 2025-11-13 01:37:36 +01:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Donncha Ó Cearbhaill
ac157a4421 WIP: Add inital scoffolding for multiple alerting levels in MVT 2023-11-28 13:38:58 +01:00
49 changed files with 605 additions and 685 deletions

View File

@@ -40,9 +40,7 @@ jobs:
- name: Safety checks
run: safety check
- name: Test with pytest and coverage
run: |
set -o pipefail
pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt
run: pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt
- name: Pytest coverage comment
continue-on-error: true # Workflows running on a fork can't post comments
uses: MishaKav/pytest-coverage-comment@main

View File

@@ -26,7 +26,7 @@ MVT supports using public [indicators of compromise (IOCs)](https://github.com/m
>
> Reliable and comprehensive digital forensic support and triage requires access to non-public indicators, research and threat intelligence.
>
>Such support is available to civil society through [Amnesty International's Security Lab](https://securitylab.amnesty.org/get-help/?c=mvt_docs) or through our forensic partnership with [Access Nows Digital Security Helpline](https://www.accessnow.org/help/).
>Such support is available to civil society through [Amnesty International's Security Lab](https://www.amnesty.org/en/tech/) or through our forensic partnership with [Access Nows Digital Security Helpline](https://www.accessnow.org/help/).
More information about using indicators of compromise with MVT is available in the [documentation](https://docs.mvt.re/en/latest/iocs/).

View File

@@ -7,27 +7,11 @@ Before proceeding, please note that MVT requires Python 3.6+ to run. While it sh
First install some basic dependencies that will be necessary to build all required tools:
```bash
sudo apt install python3 python3-venv python3-pip sqlite3 libusb-1.0-0
sudo apt install python3 python3-pip libusb-1.0-0 sqlite3
```
*libusb-1.0-0* is not required if you intend to only use `mvt-ios` and not `mvt-android`.
(Recommended) Set up `pipx`
For Ubuntu 23.04 or above:
```bash
sudo apt install pipx
pipx ensurepath
```
For Ubuntu 22.04 or below:
```
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```
Other distributions: check for a `pipx` or `python-pipx` via your package manager.
When working with Android devices you should additionally install [Android SDK Platform Tools](https://developer.android.com/studio/releases/platform-tools). If you prefer to install a package made available by your distribution of choice, please make sure the version is recent to ensure compatibility with modern Android devices.
## Dependencies on macOS
@@ -37,7 +21,7 @@ Running MVT on macOS requires Xcode and [homebrew](https://brew.sh) to be instal
In order to install dependencies use:
```bash
brew install python3 pipx libusb sqlite3
brew install python3 libusb sqlite3
```
*libusb* is not required if you intend to only use `mvt-ios` and not `mvt-android`.
@@ -58,43 +42,24 @@ It is recommended to try installing and running MVT from [Windows Subsystem Linu
## Installing MVT
### Installing from PyPI with pipx (recommended)
1. Install `pipx` following the instructions above for your OS/distribution. Make sure to run `pipx ensurepath` and open a new terminal window.
2. ```bash
pipx install mvt
```
If you haven't done so, you can add this to your `.bashrc` or `.zshrc` file in order to add locally installed PyPI binaries to your `$PATH`:
You now should have the `mvt-ios` and `mvt-android` utilities installed. If you run into problems with these commands not being found, ensure you have run `pipx ensurepath` and opened a new terminal window.
### Installing from PyPI directly into a virtual environment
You can use `pipenv`, `poetry` etc. for your virtual environment, but the provided example is with the built-in `venv` tool:
1. Create the virtual environment in a folder in the current directory named `env`:
```bash
python3 -m venv env
export PATH=$PATH:~/.local/bin
```
2. Activate the virtual environment:
Then you can install MVT directly from [PyPI](https://pypi.org/project/mvt/)
```bash
source env/bin/activate
pip3 install mvt
```
3. Install `mvt` into the virtual environment:
```bash
pip install mvt
```
The `mvt-ios` and `mvt-android` utilities should now be available as commands whenever the virtual environment is active.
### Installing from git source with pipx
If you want to have the latest features in development, you can install MVT directly from the source code in git.
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
pipx install --force git+https://github.com/mvt-project/mvt.git
git clone https://github.com/mvt-project/mvt.git
cd mvt
pip3 install .
```
You now should have the `mvt-ios` and `mvt-android` utilities installed.
**Notes:**
1. The `--force` flag is necessary to force the reinstallation of the package.
2. To revert to using a PyPI version, it will be necessary to `pipx uninstall mvt` first.

View File

@@ -21,7 +21,7 @@ MVT supports using [indicators of compromise (IOCs)](https://github.com/mvt-proj
Reliable and comprehensive digital forensic support and triage requires access to non-public indicators, research and threat intelligence.
Such support is available to civil society through [Amnesty International's Security Lab](https://securitylab.amnesty.org/get-help/?c=mvt_docs) or [Access Nows Digital Security Helpline](https://www.accessnow.org/help/).
Such support is available to civil society through [Amnesty International's Security Lab](https://securitylab.amnesty.org/contact-us/) or [Access Nows Digital Security Helpline](https://www.accessnow.org/help/).
More information about using indicators of compromise with MVT is available in the [documentation](iocs.md).

View File

@@ -45,10 +45,10 @@ Once the idevice tools are available you can check if everything works fine by c
ideviceinfo
```
This should show many details on the connected iOS device. If you are connecting the device to your laptop for the first time, it will require to unlock and enter the PIN code on the mobile device. If it complains that no device is connected and the mobile device is indeed plugged in through the USB cable, you might need to do this first, although typically the pairing is automatically done when connecting the device:
This should some many details on the connected iOS device. If you are connecting the device to your laptop for the first time, it will require to unlock and enter the PIN code on the mobile device. If it complains that no device is connected and the mobile device is indeed plugged in through the USB cable, you might need to do this first, although typically the pairing is automatically done when connecting the device:
```bash
sudo usbmuxd -f -v
sudo usbmuxd -f -d
idevicepair pair
```

View File

@@ -1,203 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import re
from typing import Any, Dict, List, Union
from mvt.android.utils import ROOT_PACKAGES
from .artifact import AndroidArtifact
class DumpsysPackagesArtifact(AndroidArtifact):
def check_indicators(self) -> None:
for result in self.results:
if result["package_name"] in ROOT_PACKAGES:
self.log.warning(
"Found an installed package related to "
'rooting/jailbreaking: "%s"',
result["package_name"],
)
self.detected.append(result)
continue
if not self.indicators:
continue
ioc = self.indicators.check_app_id(result.get("package_name", ""))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def serialize(self, record: dict) -> Union[dict, list]:
records = []
timestamps = [
{"event": "package_install", "timestamp": record["timestamp"]},
{
"event": "package_first_install",
"timestamp": record["first_install_time"],
},
{"event": "package_last_update", "timestamp": record["last_update_time"]},
]
for timestamp in timestamps:
records.append(
{
"timestamp": timestamp["timestamp"],
"module": self.__class__.__name__,
"event": timestamp["event"],
"data": f"Install or update of package {record['package_name']}",
}
)
return records
@staticmethod
def parse_dumpsys_package_for_details(output: str) -> Dict[str, Any]:
"""
Parse one entry of a dumpsys package information
"""
details = {
"uid": "",
"version_name": "",
"version_code": "",
"timestamp": "",
"first_install_time": "",
"last_update_time": "",
"permissions": [],
"requested_permissions": [],
}
in_install_permissions = False
in_runtime_permissions = False
in_declared_permissions = False
in_requested_permissions = True
for line in output.splitlines():
if in_install_permissions:
if line.startswith(" " * 4) and not line.startswith(" " * 6):
in_install_permissions = False
else:
lineinfo = line.strip().split(":")
permission = lineinfo[0]
granted = None
if "granted=" in lineinfo[1]:
granted = "granted=true" in lineinfo[1]
details["permissions"].append(
{"name": permission, "granted": granted, "type": "install"}
)
if in_runtime_permissions:
if not line.startswith(" " * 8):
in_runtime_permissions = False
else:
lineinfo = line.strip().split(":")
permission = lineinfo[0]
granted = None
if "granted=" in lineinfo[1]:
granted = "granted=true" in lineinfo[1]
details["permissions"].append(
{"name": permission, "granted": granted, "type": "runtime"}
)
if in_declared_permissions:
if not line.startswith(" " * 6):
in_declared_permissions = False
else:
permission = line.strip().split(":")[0]
details["permissions"].append(
{"name": permission, "type": "declared"}
)
if in_requested_permissions:
if not line.startswith(" " * 6):
in_requested_permissions = False
else:
details["requested_permissions"].append(line.strip())
if line.strip().startswith("userId="):
details["uid"] = line.split("=")[1].strip()
elif line.strip().startswith("versionName="):
details["version_name"] = line.split("=")[1].strip()
elif line.strip().startswith("versionCode="):
details["version_code"] = line.split("=", 1)[1].strip()
elif line.strip().startswith("timeStamp="):
details["timestamp"] = line.split("=")[1].strip()
elif line.strip().startswith("firstInstallTime="):
details["first_install_time"] = line.split("=")[1].strip()
elif line.strip().startswith("lastUpdateTime="):
details["last_update_time"] = line.split("=")[1].strip()
elif line.strip() == "install permissions:":
in_install_permissions = True
elif line.strip() == "runtime permissions:":
in_runtime_permissions = True
elif line.strip() == "declared permissions:":
in_declared_permissions = True
elif line.strip() == "requested permissions:":
in_requested_permissions = True
return details
def parse_dumpsys_packages(self, output: str) -> List[Dict[str, Any]]:
"""
Parse the dumpsys package service data
"""
pkg_rxp = re.compile(r" Package \[(.+?)\].*")
results = []
package_name = None
package = {}
lines = []
for line in output.splitlines():
if line.startswith(" Package ["):
if len(lines) > 0:
details = self.parse_dumpsys_package_for_details("\n".join(lines))
package.update(details)
results.append(package)
lines = []
package = {}
matches = pkg_rxp.findall(line)
if not matches:
continue
package_name = matches[0]
package["package_name"] = package_name
continue
if not package_name:
continue
lines.append(line)
if len(lines) > 0:
details = self.parse_dumpsys_package_for_details("\n".join(lines))
package.update(details)
results.append(package)
return results
def parse(self, content: str):
"""
Parse the Dumpsys Package section for activities
Adds results to self.results
:param content: content of the package section (string)
"""
self.results = []
package = []
in_package_list = False
for line in content.split("\n"):
if line.startswith("Packages:"):
in_package_list = True
continue
if not in_package_list:
continue
if line.strip() == "":
break
package.append(line)
self.results = self.parse_dumpsys_packages("\n".join(package))

View File

@@ -72,7 +72,7 @@ def version():
is_flag=True,
help="Extract all packages installed on the phone, including system packages",
)
@click.option("--virustotal", "-V", is_flag=True, help="Check packages on VirusTotal")
@click.option("--virustotal", "-v", is_flag=True, help="Check packages on VirusTotal")
@click.option(
"--output",
"-o",

View File

@@ -4,25 +4,86 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import List, Optional, Union
from rich.console import Console
from rich.progress import track
from rich.table import Table
from rich.text import Text
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
from mvt.android.utils import (
DANGEROUS_PERMISSIONS,
DANGEROUS_PERMISSIONS_THRESHOLD,
ROOT_PACKAGES,
SECURITY_PACKAGES,
SYSTEM_UPDATE_PACKAGES,
)
from mvt.android.parsers.dumpsys import parse_dumpsys_package_for_details
from mvt.common.virustotal import VTNoKey, VTQuotaExceeded, virustotal_lookup
from .base import AndroidExtraction
DANGEROUS_PERMISSIONS_THRESHOLD = 10
DANGEROUS_PERMISSIONS = [
"android.permission.ACCESS_COARSE_LOCATION",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.AUTHENTICATE_ACCOUNTS",
"android.permission.CAMERA",
"android.permission.DISABLE_KEYGUARD",
"android.permission.PROCESS_OUTGOING_CALLS",
"android.permission.READ_CALENDAR",
"android.permission.READ_CALL_LOG",
"android.permission.READ_CONTACTS",
"android.permission.READ_PHONE_STATE",
"android.permission.READ_SMS",
"android.permission.RECEIVE_MMS",
"android.permission.RECEIVE_SMS",
"android.permission.RECEIVE_WAP_PUSH",
"android.permission.RECORD_AUDIO",
"android.permission.SEND_SMS",
"android.permission.SYSTEM_ALERT_WINDOW",
"android.permission.USE_CREDENTIALS",
"android.permission.USE_SIP",
"com.android.browser.permission.READ_HISTORY_BOOKMARKS",
]
ROOT_PACKAGES: List[str] = [
"com.noshufou.android.su",
"com.noshufou.android.su.elite",
"eu.chainfire.supersu",
"com.koushikdutta.superuser",
"com.thirdparty.superuser",
"com.yellowes.su",
"com.koushikdutta.rommanager",
"com.koushikdutta.rommanager.license",
"com.dimonvideo.luckypatcher",
"com.chelpus.lackypatch",
"com.ramdroid.appquarantine",
"com.ramdroid.appquarantinepro",
"com.devadvance.rootcloak",
"com.devadvance.rootcloakplus",
"de.robv.android.xposed.installer",
"com.saurik.substrate",
"com.zachspong.temprootremovejb",
"com.amphoras.hidemyroot",
"com.amphoras.hidemyrootadfree",
"com.formyhm.hiderootPremium",
"com.formyhm.hideroot",
"me.phh.superuser",
"eu.chainfire.supersu.pro",
"com.kingouser.com",
"com.topjohnwu.magisk",
]
SECURITY_PACKAGES = [
"com.policydm",
"com.samsung.android.app.omcagent",
"com.samsung.android.securitylogagent",
"com.sec.android.soagent",
]
SYSTEM_UPDATE_PACKAGES = [
"com.android.updater",
"com.google.android.gms",
"com.huawei.android.hwouc",
"com.lge.lgdmsclient",
"com.motorola.ccc.ota",
"com.oneplus.opbackup",
"com.oppo.ota",
"com.transsion.systemupdate",
"com.wssyncmldm",
]
class Packages(AndroidExtraction):
"""This module extracts the list of installed packages."""
@@ -173,9 +234,7 @@ class Packages(AndroidExtraction):
if line.strip() == "Packages:":
in_packages = True
return DumpsysPackagesArtifact.parse_dumpsys_package_for_details(
"\n".join(lines)
)
return parse_dumpsys_package_for_details("\n".join(lines))
def _get_files_for_package(self, package_name: str) -> list:
command = f"pm path {package_name}"

View File

@@ -12,7 +12,7 @@ from .base import AndroidQFModule
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidQFModule):
"""This module analyses dumpsys accessibility"""
"""This module analyse dumpsys accessbility"""
def __init__(
self,

View File

@@ -4,18 +4,19 @@
# https://license.mvt.re/1.1/
import logging
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Union
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
from mvt.android.modules.adb.packages import (
DANGEROUS_PERMISSIONS,
DANGEROUS_PERMISSIONS_THRESHOLD,
ROOT_PACKAGES,
)
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
from .base import AndroidQFModule
class DumpsysPackages(DumpsysPackagesArtifact, AndroidQFModule):
class DumpsysPackages(AndroidQFModule):
"""This module analyse dumpsys packages"""
def __init__(
@@ -36,15 +37,70 @@ class DumpsysPackages(DumpsysPackagesArtifact, AndroidQFModule):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
entries = []
for entry in ["timestamp", "first_install_time", "last_update_time"]:
if entry in record:
entries.append(
{
"timestamp": record[entry],
"module": self.__class__.__name__,
"event": entry,
"data": f"Package {record['package_name']} "
f"({record['uid']})",
}
)
return entries
def check_indicators(self) -> None:
for result in self.results:
if result["package_name"] in ROOT_PACKAGES:
self.log.warning(
"Found an installed package related to "
'rooting/jailbreaking: "%s"',
result["package_name"],
)
self.detected.append(result)
continue
if not self.indicators:
continue
ioc = self.indicators.check_app_id(result.get("package_name", ""))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if len(dumpsys_file) != 1:
self.log.info("Dumpsys file not found")
return
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
self.parse(content)
data = self._get_file_content(dumpsys_file[0])
package = []
in_service = False
in_package_list = False
for line in data.decode("utf-8").split("\n"):
if line.strip().startswith("DUMP OF SERVICE package:"):
in_service = True
continue
if in_service and line.startswith("Packages:"):
in_package_list = True
continue
if not in_service or not in_package_list:
continue
if line.strip() == "":
break
package.append(line)
self.results = parse_dumpsys_packages("\n".join(package))
for result in self.results:
dangerous_permissions_count = 0

View File

@@ -4,15 +4,19 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from typing import Optional, Union
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
from mvt.android.utils import DANGEROUS_PERMISSIONS, DANGEROUS_PERMISSIONS_THRESHOLD
from mvt.android.modules.adb.packages import (
DANGEROUS_PERMISSIONS,
DANGEROUS_PERMISSIONS_THRESHOLD,
ROOT_PACKAGES,
)
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
from .base import BugReportModule
class Packages(DumpsysPackagesArtifact, BugReportModule):
class Packages(BugReportModule):
"""This module extracts details on receivers for risky activities."""
def __init__(
@@ -33,18 +37,83 @@ class Packages(DumpsysPackagesArtifact, BugReportModule):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
records = []
timestamps = [
{"event": "package_install", "timestamp": record["timestamp"]},
{
"event": "package_first_install",
"timestamp": record["first_install_time"],
},
{"event": "package_last_update", "timestamp": record["last_update_time"]},
]
for timestamp in timestamps:
records.append(
{
"timestamp": timestamp["timestamp"],
"module": self.__class__.__name__,
"event": timestamp["event"],
"data": f"Install or update of package {record['package_name']}",
}
)
return records
def check_indicators(self) -> None:
for result in self.results:
if result["package_name"] in ROOT_PACKAGES:
self.log.warning(
"Found an installed package related to "
'rooting/jailbreaking: "%s"',
result["package_name"],
)
self.detected.append(result)
continue
if not self.indicators:
continue
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
def run(self) -> None:
data = self._get_dumpstate_file()
if not data:
content = self._get_dumpstate_file()
if not content:
self.log.error(
"Unable to find dumpstate file. "
"Did you provide a valid bug report archive?"
)
return
data = data.decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
self.parse(content)
in_package = False
in_packages_list = False
lines = []
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE package:":
in_package = True
continue
if not in_package:
continue
if line.strip() == "Packages:":
in_packages_list = True
continue
if not in_packages_list:
continue
if line.strip() == "":
break
lines.append(line)
self.results = parse_dumpsys_packages("\n".join(lines))
for result in self.results:
dangerous_permissions_count = 0

View File

@@ -0,0 +1,131 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import re
from typing import Any, Dict, List
def parse_dumpsys_package_for_details(output: str) -> Dict[str, Any]:
"""
Parse one entry of a dumpsys package information
"""
details = {
"uid": "",
"version_name": "",
"version_code": "",
"timestamp": "",
"first_install_time": "",
"last_update_time": "",
"permissions": [],
"requested_permissions": [],
}
in_install_permissions = False
in_runtime_permissions = False
in_declared_permissions = False
in_requested_permissions = True
for line in output.splitlines():
if in_install_permissions:
if line.startswith(" " * 4) and not line.startswith(" " * 6):
in_install_permissions = False
else:
lineinfo = line.strip().split(":")
permission = lineinfo[0]
granted = None
if "granted=" in lineinfo[1]:
granted = "granted=true" in lineinfo[1]
details["permissions"].append(
{"name": permission, "granted": granted, "type": "install"}
)
if in_runtime_permissions:
if not line.startswith(" " * 8):
in_runtime_permissions = False
else:
lineinfo = line.strip().split(":")
permission = lineinfo[0]
granted = None
if "granted=" in lineinfo[1]:
granted = "granted=true" in lineinfo[1]
details["permissions"].append(
{"name": permission, "granted": granted, "type": "runtime"}
)
if in_declared_permissions:
if not line.startswith(" " * 6):
in_declared_permissions = False
else:
permission = line.strip().split(":")[0]
details["permissions"].append({"name": permission, "type": "declared"})
if in_requested_permissions:
if not line.startswith(" " * 6):
in_requested_permissions = False
else:
details["requested_permissions"].append(line.strip())
if line.strip().startswith("userId="):
details["uid"] = line.split("=")[1].strip()
elif line.strip().startswith("versionName="):
details["version_name"] = line.split("=")[1].strip()
elif line.strip().startswith("versionCode="):
details["version_code"] = line.split("=", 1)[1].strip()
elif line.strip().startswith("timeStamp="):
details["timestamp"] = line.split("=")[1].strip()
elif line.strip().startswith("firstInstallTime="):
details["first_install_time"] = line.split("=")[1].strip()
elif line.strip().startswith("lastUpdateTime="):
details["last_update_time"] = line.split("=")[1].strip()
elif line.strip() == "install permissions:":
in_install_permissions = True
elif line.strip() == "runtime permissions:":
in_runtime_permissions = True
elif line.strip() == "declared permissions:":
in_declared_permissions = True
elif line.strip() == "requested permissions:":
in_requested_permissions = True
return details
def parse_dumpsys_packages(output: str) -> List[Dict[str, Any]]:
"""
Parse the dumpsys package service data
"""
pkg_rxp = re.compile(r" Package \[(.+?)\].*")
results = []
package_name = None
package = {}
lines = []
for line in output.splitlines():
if line.startswith(" Package ["):
if len(lines) > 0:
details = parse_dumpsys_package_for_details("\n".join(lines))
package.update(details)
results.append(package)
lines = []
package = {}
matches = pkg_rxp.findall(line)
if not matches:
continue
package_name = matches[0]
package["package_name"] = package_name
continue
if not package_name:
continue
lines.append(line)
if len(lines) > 0:
details = parse_dumpsys_package_for_details("\n".join(lines))
package.update(details)
results.append(package)
return results

View File

@@ -3,7 +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/
from datetime import datetime, timedelta
from typing import List
def warn_android_patch_level(patch_level: str, log) -> bool:
@@ -18,76 +17,3 @@ def warn_android_patch_level(patch_level: str, log) -> bool:
return True
return False
ROOT_PACKAGES: List[str] = [
"com.noshufou.android.su",
"com.noshufou.android.su.elite",
"eu.chainfire.supersu",
"com.koushikdutta.superuser",
"com.thirdparty.superuser",
"com.yellowes.su",
"com.koushikdutta.rommanager",
"com.koushikdutta.rommanager.license",
"com.dimonvideo.luckypatcher",
"com.chelpus.lackypatch",
"com.ramdroid.appquarantine",
"com.ramdroid.appquarantinepro",
"com.devadvance.rootcloak",
"com.devadvance.rootcloakplus",
"de.robv.android.xposed.installer",
"com.saurik.substrate",
"com.zachspong.temprootremovejb",
"com.amphoras.hidemyroot",
"com.amphoras.hidemyrootadfree",
"com.formyhm.hiderootPremium",
"com.formyhm.hideroot",
"me.phh.superuser",
"eu.chainfire.supersu.pro",
"com.kingouser.com",
"com.topjohnwu.magisk",
]
DANGEROUS_PERMISSIONS_THRESHOLD = 10
DANGEROUS_PERMISSIONS = [
"android.permission.ACCESS_COARSE_LOCATION",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.AUTHENTICATE_ACCOUNTS",
"android.permission.CAMERA",
"android.permission.DISABLE_KEYGUARD",
"android.permission.PROCESS_OUTGOING_CALLS",
"android.permission.READ_CALENDAR",
"android.permission.READ_CALL_LOG",
"android.permission.READ_CONTACTS",
"android.permission.READ_PHONE_STATE",
"android.permission.READ_SMS",
"android.permission.RECEIVE_MMS",
"android.permission.RECEIVE_SMS",
"android.permission.RECEIVE_WAP_PUSH",
"android.permission.RECORD_AUDIO",
"android.permission.SEND_SMS",
"android.permission.SYSTEM_ALERT_WINDOW",
"android.permission.USE_CREDENTIALS",
"android.permission.USE_SIP",
"com.android.browser.permission.READ_HISTORY_BOOKMARKS",
]
SECURITY_PACKAGES = [
"com.policydm",
"com.samsung.android.app.omcagent",
"com.samsung.android.securitylogagent",
"com.sec.android.soagent",
]
SYSTEM_UPDATE_PACKAGES = [
"com.android.updater",
"com.google.android.gms",
"com.huawei.android.hwouc",
"com.lge.lgdmsclient",
"com.motorola.ccc.ota",
"com.oneplus.opbackup",
"com.oppo.ota",
"com.transsion.systemupdate",
"com.wssyncmldm",
]

138
mvt/common/alerting.py Normal file
View File

@@ -0,0 +1,138 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from enum import Enum
class AlertLevel(Enum):
"""
informational: Rule is intended for enrichment of events, e.g. by tagging them. No case or alerting should be triggered by such rules because it is expected that a huge amount of events will match these rules.
low: Notable event but rarely an incident. Low rated events can be relevant in high numbers or combination with others. Immediate reaction shouldnt be necessary, but a regular review is recommended.
medium: Relevant event that should be reviewed manually on a more frequent basis.
high: Relevant event that should trigger an internal alert and requires a prompt review.
critical: Highly relevant event that indicates an incident. Critical events should be reviewed immediately.
"""
INFORMATIONAL = 0
LOW = 10
MEDIUM = 20
HIGH = 30
CRITICAL = 40
class AlertStore(object):
"""
Track all of the alerts and detections generated during an analysis.
Results can be logged as log messages or in JSON format for processing by other tools.
"""
def __init__(self) -> None:
self.alerts = []
def add_alert(
self, level, message=None, event_time=None, event=None, ioc=None, detected=True
):
"""
Add an alert to the alert store.
"""
self.alerts.append(
Alert(
level=level,
message=message,
event_time=event_time,
event=event,
ioc=ioc,
detected=detected,
)
)
def informational(
self, message=None, event_time=None, event=None, ioc=None, detected=False
):
self.add_alert(
AlertLevel.INFORMATIONAL,
message=message,
event_time=event_time,
event=event,
ioc=ioc,
detected=detected,
)
def low(self, message=None, event_time=None, event=None, ioc=None, detected=False):
self.add_alert(
AlertLevel.LOW,
message=message,
event_time=event_time,
event=event,
ioc=ioc,
detected=detected,
)
def medium(
self, message=None, event_time=None, event=None, ioc=None, detected=False
):
self.add_alert(
AlertLevel.MEDIUM,
message=message,
event_time=event_time,
event=event,
ioc=ioc,
detected=detected,
)
def high(self, message=None, event_time=None, event=None, ioc=None, detected=False):
self.add_alert(
AlertLevel.HIGH,
message=message,
event_time=event_time,
event=event,
ioc=ioc,
detected=detected,
)
def critical(
self, message=None, event_time=None, event=None, ioc=None, detected=False
):
self.add_alert(
AlertLevel.CRITICAL,
message=message,
event_time=event_time,
event=event,
ioc=ioc,
detected=detected,
)
class Alert(object):
"""
An alert generated by an MVT module.
"""
def __init__(self, level, message, event_time, event, ioc, detected):
self.level = level
self.message = message
self.event_time = event_time
self.event = event
self.ioc = ioc
self.detected = detected
def __repr__(self):
return f"<Alert level={self.level} message={self.message} event_time={self.event_time} event={self.event}>"
def __str__(self):
return f"{self.level} {self.message} {self.event_time} {self.event}"
def to_log(self):
return f"{self.level} {self.message} {self.event_time} {self.event}"
def to_json(self):
return {
"level": self.level,
"message": self.message,
"event_time": self.event_time,
"event": self.event,
"ioc": self.ioc,
"detected": self.detected,
}

View File

@@ -160,27 +160,6 @@ class Command:
def finish(self) -> None:
raise NotImplementedError
def _show_disable_adb_warning(self) -> None:
"""Warn if ADB is enabled"""
if type(self).__name__ in ["CmdAndroidCheckADB", "CmdAndroidCheckAndroidQF"]:
self.log.info(
"Please disable Developer Options and ADB (Android Debug Bridge) on the device once finished with the acquisition. "
"ADB is a powerful tool which can allow unauthorized access to the device."
)
def _show_support_message(self) -> None:
support_message = "Please seek reputable expert help if you have serious concerns about a possible spyware attack. Such support is available to human rights defenders and civil society through Amnesty International's Security Lab at https://securitylab.amnesty.org/get-help/?c=mvt"
if self.detected_count == 0:
self.log.info(
f"[bold]NOTE:[/bold] Using MVT with public indicators of compromise (IOCs) [bold]WILL NOT[/bold] automatically detect advanced attacks.\n\n{support_message}",
extra={"markup": True},
)
else:
self.log.warning(
f"[bold]NOTE: Detected indicators of compromise[/bold]. Only expert review can confirm if the detected indicators are signs of an attack.\n\n{support_message}",
extra={"markup": True},
)
def run(self) -> None:
try:
self.init()
@@ -229,6 +208,3 @@ class Command:
self._store_timeline()
self._store_info()
self._show_disable_adb_warning()
self._show_support_message()

View File

@@ -10,7 +10,7 @@ from .version import MVT_VERSION
def check_updates() -> None:
# First we check for MVT version updates.
# First we check for MVT version udpates.
mvt_updates = MVTUpdates()
try:
latest_version = mvt_updates.check()

File diff suppressed because it is too large Load Diff

View File

@@ -53,20 +53,20 @@ def convert_chrometime_to_datetime(timestamp: int) -> datetime.datetime:
def convert_datetime_to_iso(date_time: datetime.datetime) -> str:
"""Converts datetime to ISO string.
:param datetime: datetime, naive or timezone aware
:param datetime: datetime.
:type datetime: datetime.datetime
:returns: ISO datetime string in YYYY-mm-dd HH:MM:SS.ms format.
:rtype: str
"""
if date_time.tzinfo:
# Timezone aware object - convert to UTC
date_time = date_time.astimezone(tz=datetime.timezone.utc)
return date_time.strftime("%Y-%m-%d %H:%M:%S.%f")
try:
return date_time.strftime("%Y-%m-%d %H:%M:%S.%f")
except Exception:
return ""
def convert_unix_to_utc_datetime(
timestamp: Union[int, float, str],
timestamp: Union[int, float, str]
) -> datetime.datetime:
"""Converts a unix epoch timestamp to UTC datetime.

View File

@@ -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.5.3"
MVT_VERSION = "2.4.3"

View File

@@ -169,7 +169,7 @@
},
{
"identifier": "iPhone14,8",
"description": "iPhone 14 Plus"
"decription": "iPhone 14 Plus"
},
{
"identifier": "iPhone15,2",

View File

@@ -883,14 +883,6 @@
"version": "15.8",
"build": "19H370"
},
{
"version": "15.8.1",
"build": "19H380"
},
{
"version": "15.8.2",
"build": "19H384"
},
{
"build": "20A362",
"version": "16.0"
@@ -968,26 +960,6 @@
"version": "16.7.2",
"build": "20H115"
},
{
"version": "16.7.3",
"build": "20H232"
},
{
"version": "16.7.4",
"build": "20H240"
},
{
"version": "16.7.5",
"build": "20H307"
},
{
"version": "16.7.6",
"build": "20H320"
},
{
"version": "16.7.7",
"build": "20H330"
},
{
"version": "17.0",
"build": "21A327"
@@ -1023,37 +995,5 @@
{
"version": "17.1.1",
"build": "21B91"
},
{
"version": "17.1.2",
"build": "21B101"
},
{
"version": "17.2",
"build": "21C62"
},
{
"version": "17.2.1",
"build": "21C66"
},
{
"version": "17.3",
"build": "21D50"
},
{
"version": "17.3.1",
"build": "21D61"
},
{
"version": "17.4",
"build": "21E219"
},
{
"version": "17.4.1",
"build": "21E236"
},
{
"version": "17.4.1",
"build": "21E237"
}
]

View File

@@ -8,6 +8,7 @@ import io
import logging
import os
import plistlib
import sqlite3
from typing import Optional
from mvt.common.module import DatabaseNotFoundError
@@ -123,7 +124,7 @@ class Manifest(IOSExtraction):
self.log.info("Found Manifest.db database at path: %s", manifest_db_path)
conn = self._open_sqlite_db(manifest_db_path)
conn = sqlite3.connect(manifest_db_path)
cur = conn.cursor()
cur.execute("SELECT * FROM Files;")

View File

@@ -49,7 +49,7 @@ class IOSExtraction(MVTModule):
"""
# TODO: Find a better solution.
if not forced:
conn = self._open_sqlite_db(file_path)
conn = sqlite3.connect(file_path)
cur = conn.cursor()
try:
@@ -91,9 +91,6 @@ class IOSExtraction(MVTModule):
self.log.info("Database at path %s recovered successfully!", file_path)
def _open_sqlite_db(self, file_path: str) -> sqlite3.Connection:
return sqlite3.connect(f"file:{file_path}?immutable=1", uri=True)
def _get_backup_files_from_manifest(
self, relative_path: Optional[str] = None, domain: Optional[str] = None
) -> Iterator[dict]:
@@ -112,7 +109,7 @@ class IOSExtraction(MVTModule):
base_sql = "SELECT fileID, domain, relativePath FROM Files WHERE "
try:
conn = self._open_sqlite_db(manifest_db_path)
conn = sqlite3.connect(manifest_db_path)
cur = conn.cursor()
if relative_path and domain:
cur.execute(

View File

@@ -85,7 +85,7 @@ class Analytics(IOSExtraction):
def _extract_analytics_data(self):
artifact = self.file_path.split("/")[-1]
conn = self._open_sqlite_db(self.file_path)
conn = sqlite3.connect(self.file_path)
cur = conn.cursor()
try:

View File

@@ -64,7 +64,7 @@ class CacheFiles(IOSExtraction):
def _process_cache_file(self, file_path):
self.log.info("Processing cache file at path: %s", file_path)
conn = self._open_sqlite_db(file_path)
conn = sqlite3.connect(file_path)
cur = conn.cursor()
try:

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/
import logging
import sqlite3
from typing import Optional, Union
from mvt.common.utils import convert_mactime_to_iso
@@ -60,7 +61,7 @@ class SafariFavicon(IOSExtraction):
self.detected.append(result)
def _process_favicon_db(self, file_path):
conn = self._open_sqlite_db(file_path)
conn = sqlite3.connect(file_path)
# Fetch valid icon cache.
cur = conn.cursor()

View File

@@ -70,9 +70,6 @@ class ShutdownLog(IOSExtraction):
def process_shutdownlog(self, content):
current_processes = []
recent_processes = []
times_delayed = 0
delay = 0.0
for line in content.split("\n"):
line = line.strip()
@@ -81,22 +78,9 @@ class ShutdownLog(IOSExtraction):
{
"pid": line[line.find("pid: ") + 5 : line.find(" (")],
"client": line[line.find("(") + 1 : line.find(")")],
"delay": delay,
"times_delayed": times_delayed,
}
)
elif line.startswith("After "):
# Consider the previous processes
# End of the current processes
for p in current_processes:
recent_processes.append(p)
delay += float(line.split(" ")[1][:-2])
times_delayed += 1
current_processes = []
elif line.startswith("SIGTERM: "):
for p in current_processes:
recent_processes.append(p)
try:
mac_timestamp = int(line[line.find("[") + 1 : line.find("]")])
except ValueError:
@@ -108,21 +92,16 @@ class ShutdownLog(IOSExtraction):
isodate = convert_mactime_to_iso(mac_timestamp, from_2001=False)
for process in recent_processes:
for current_process in current_processes:
self.results.append(
{
"isodate": isodate,
"pid": process["pid"],
"client": process["client"],
"delay": process["delay"],
"times_delayed": process["times_delayed"],
"pid": current_process["pid"],
"client": current_process["client"],
}
)
current_processes = []
recent_processes = []
times_delayed = 0
delay = 0.0
self.results = sorted(self.results, key=lambda entry: entry["isodate"])

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/
import logging
import sqlite3
from typing import Optional, Union
from mvt.common.utils import convert_mactime_to_iso
@@ -81,7 +82,7 @@ class Calendar(IOSExtraction):
"""
Parse the calendar database
"""
conn = self._open_sqlite_db(self.file_path)
conn = sqlite3.connect(self.file_path)
cur = conn.cursor()
cur.execute(

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/
import logging
import sqlite3
from typing import Optional, Union
from mvt.common.utils import convert_mactime_to_iso
@@ -52,7 +53,7 @@ class Calls(IOSExtraction):
)
self.log.info("Found Calls database at path: %s", self.file_path)
conn = self._open_sqlite_db(self.file_path)
conn = sqlite3.connect(self.file_path)
cur = conn.cursor()
cur.execute(
"""
@@ -69,9 +70,9 @@ class Calls(IOSExtraction):
"isodate": convert_mactime_to_iso(row[0]),
"duration": row[1],
"location": row[2],
"number": (
row[3].decode("utf-8") if row[3] and row[3] is bytes else row[3]
),
"number": row[3].decode("utf-8")
if row[3] and row[3] is bytes
else row[3],
"provider": row[4],
}
)

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/
import logging
import sqlite3
from typing import Optional, Union
from mvt.common.utils import convert_chrometime_to_datetime, convert_datetime_to_iso
@@ -65,7 +66,7 @@ class ChromeFavicon(IOSExtraction):
)
self.log.info("Found Chrome favicon cache database at path: %s", self.file_path)
conn = self._open_sqlite_db(self.file_path)
conn = sqlite3.connect(self.file_path)
# Fetch icon cache
cur = conn.cursor()

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/
import logging
import sqlite3
from typing import Optional, Union
from mvt.common.utils import convert_chrometime_to_datetime, convert_datetime_to_iso
@@ -66,7 +67,7 @@ class ChromeHistory(IOSExtraction):
)
self.log.info("Found Chrome history database at path: %s", self.file_path)
conn = self._open_sqlite_db(self.file_path)
conn = sqlite3.connect(self.file_path)
cur = conn.cursor()
cur.execute(
"""

View File

@@ -44,7 +44,7 @@ class Contacts(IOSExtraction):
)
self.log.info("Found Contacts database at path: %s", self.file_path)
conn = self._open_sqlite_db(self.file_path)
conn = sqlite3.connect(self.file_path)
cur = conn.cursor()
try:
cur.execute(

Some files were not shown because too many files have changed in this diff Show More