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

Compare commits

...

14 Commits

Author SHA1 Message Date
Nex
660e208473 Bumped version 2021-09-28 15:40:26 +02:00
Nex
01e68ccc6a Fixed dict decl 2021-09-28 12:45:15 +02:00
Nex
fba0fa1f2c Removed newline 2021-09-28 12:44:15 +02:00
Nex
1cbf55e50e Merge branch 'pungentsneak-main' 2021-09-28 12:43:26 +02:00
Nex
8fcc79ebfa Adapted for better support 2021-09-28 12:42:57 +02:00
Nex
423462395a Merge branch 'main' of https://github.com/pungentsneak/mvt into pungentsneak-main 2021-09-28 12:33:14 +02:00
Nex
1f08572a6a Bumped version 2021-09-22 17:32:22 +02:00
Nex
94e3c0ce7b Added iOS 15.0 2021-09-22 17:27:29 +02:00
pungentsneak
904daad935 add ShutdownLog 2021-09-22 13:24:17 +02:00
Nex
eb2a8b8b41 Merge branch 'Te-k-stalkerware' 2021-09-21 22:27:54 +02:00
Nex
60a17381a2 Standardized code 2021-09-21 22:27:35 +02:00
tek
ef2bb93dc4 Adds indicator check for android package name and file hash 2021-09-21 19:43:02 +02:00
Nex
f68b7e7089 Pull file hashes fom Packages module directly 2021-09-20 19:15:39 +02:00
Nex
a22241ec32 Added version commands 2021-09-17 14:19:03 +02:00
14 changed files with 185 additions and 43 deletions

View File

@@ -230,6 +230,18 @@ If indicators are provided through the command-line, they are checked against th
---
### `shutdown_log.json`
!!! info "Availability"
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.
If indicators are provided through the command-line, they are checked against the paths. Any matches are stored in *shutdown_log_detected.json*.
---
### `sms.json`
!!! info "Availability"

View File

@@ -34,6 +34,14 @@ def cli():
logo()
#==============================================================================
# Command: version
#==============================================================================
@cli.command("version", help="Show the currently installed version of MVT")
def version():
return
#==============================================================================
# Download APKs
#==============================================================================

View File

@@ -11,7 +11,6 @@ import pkg_resources
from tqdm import tqdm
from mvt.common.module import InsufficientPrivileges
from mvt.common.utils import get_sha256_from_file_path
from .modules.adb.base import AndroidExtraction
from .modules.adb.packages import Packages
@@ -158,37 +157,16 @@ class DownloadAPKs(AndroidExtraction):
log.info("[%d/%d] Package: %s", counter, len(packages_selection),
package["package_name"])
# Get the file path for the specific package.
try:
output = self._adb_command(f"pm path {package['package_name']}")
output = output.strip().replace("package:", "")
if not output:
continue
except Exception as e:
log.exception("Failed to get path of package %s: %s",
package["package_name"], e)
self._adb_reconnect()
continue
# Sometimes the package path contains multiple lines for multiple apks.
# We loop through each line and download each file.
for path in output.split("\n"):
device_path = path.strip()
file_path = self.pull_package_file(package["package_name"],
device_path)
if not file_path:
for package_file in package["files"]:
device_path = package_file["path"]
local_path = self.pull_package_file(package["package_name"],
device_path)
if not local_path:
continue
file_info = {
"path": device_path,
"local_name": file_path,
"sha256": get_sha256_from_file_path(file_path),
}
if "files" not in package:
package["files"] = [file_info,]
else:
package["files"].append(file_info)
package_file["local_path"] = local_path
log.info("Download of selected packages completed")

View File

@@ -32,7 +32,7 @@ def koodous_lookup(packages):
res = requests.get(url)
report = res.json()
row = [package["package_name"], file["local_name"]]
row = [package["package_name"], file["path"]]
if "package_name" in report:
trusted = "no"

View File

@@ -75,7 +75,7 @@ def virustotal_lookup(packages):
for package in packages:
for file in package.get("files", []):
row = [package["package_name"], file["local_name"]]
row = [package["package_name"], file["path"]]
if file["sha256"] in detections:
detection = detections[file["sha256"]]

View File

@@ -132,7 +132,7 @@ class AndroidExtraction(MVTModule):
"""
return self._adb_command(f"su -c {command}")
def _adb_check_file_exists(self, file):
"""Verify that a file exists.
@@ -166,7 +166,7 @@ class AndroidExtraction(MVTModule):
self._adb_download_root(remote_path, local_path, progress_callback)
else:
raise Exception(f"Unable to download file {remote_path}: {e}")
def _adb_download_root(self, remote_path, local_path, progress_callback=None):
try:
# Check if we have root, if not raise an Exception.
@@ -191,7 +191,7 @@ class AndroidExtraction(MVTModule):
# Delete the copy on /sdcard/.
self._adb_command(f"rm -rf {new_remote_path}")
except AdbCommandFailureException as e:
raise Exception(f"Unable to download file {remote_path}: {e}")

View File

@@ -33,6 +33,14 @@ class ChromeHistory(AndroidExtraction):
"data": f"{record['id']} - {record['url']} (visit ID: {record['visit_id']}, redirect source: {record['redirect_source']})"
}
def check_indicators(self):
if not self.indicators:
return
for result in self.results:
if self.indicators.check_domain(result["url"]):
self.detected.append(result)
def _parse_db(self, db_path):
"""Parse a Chrome History database file.

View File

@@ -44,16 +44,49 @@ class Packages(AndroidExtraction):
root_packages_path = os.path.join("..", "..", "data", "root_packages.txt")
root_packages_string = pkg_resources.resource_string(__name__, root_packages_path)
root_packages = root_packages_string.decode("utf-8").split("\n")
root_packages = [rp.strip() for rp in root_packages]
for root_package in root_packages:
root_package = root_package.strip()
if not root_package:
continue
if root_package in self.results:
for result in self.results:
if result["package_name"] in root_packages:
self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"",
root_package)
self.detected.append(root_package)
result["package_name"])
self.detected.append(result)
if result["package_name"] in self.indicators.ioc_app_ids:
self.log.warning("Found a malicious package name: \"%s\"",
result["package_name"])
self.detected.append(result)
for file in result["files"]:
if file["sha256"] in self.indicators.ioc_files_sha256:
self.log.warning("Found a malicious APK: \"%s\" %s",
result["package_name"],
file["sha256"])
self.detected.append(result)
def _get_files_for_package(self, package_name):
output = self._adb_command(f"pm path {package_name}")
output = output.strip().replace("package:", "")
if not output:
return []
package_files = []
for file_path in output.split("\n"):
file_path = file_path.strip()
md5 = self._adb_command(f"md5sum {file_path}").split(" ")[0]
sha1 = self._adb_command(f"sha1sum {file_path}").split(" ")[0]
sha256 = self._adb_command(f"sha256sum {file_path}").split(" ")[0]
sha512 = self._adb_command(f"sha512sum {file_path}").split(" ")[0]
package_files.append({
"path": file_path,
"md5": md5,
"sha1": sha1,
"sha256": sha256,
"sha512": sha512,
})
return package_files
def run(self):
self._adb_connect()
@@ -85,6 +118,8 @@ class Packages(AndroidExtraction):
first_install = dumpsys[1].split("=")[1].strip()
last_update = dumpsys[2].split("=")[1].strip()
package_files = self._get_files_for_package(package_name)
self.results.append({
"package_name": package_name,
"file_name": file_name,
@@ -96,6 +131,7 @@ class Packages(AndroidExtraction):
"disabled": False,
"system": False,
"third_party": False,
"files": package_files,
})
cmds = [

View File

@@ -23,6 +23,8 @@ class Indicators:
self.ioc_processes = []
self.ioc_emails = []
self.ioc_files = []
self.ioc_files_sha256 = []
self.ioc_app_ids = []
self.ioc_count = 0
def _add_indicator(self, ioc, iocs_list):
@@ -66,6 +68,12 @@ class Indicators:
elif key == "file:name":
self._add_indicator(ioc=value,
iocs_list=self.ioc_files)
elif key == "app:id":
self._add_indicator(ioc=value,
iocs_list=self.ioc_app_ids)
elif key == "file:hashes.sha256":
self._add_indicator(ioc=value,
iocs_list=self.ioc_files_sha256)
def check_domain(self, url) -> bool:
"""Check if a given URL matches any of the provided domain indicators.

View File

@@ -6,7 +6,7 @@
import requests
from packaging import version
MVT_VERSION = "1.2.8"
MVT_VERSION = "1.2.10"
def check_for_updates():
res = requests.get("https://pypi.org/pypi/mvt/json")

View File

@@ -38,6 +38,14 @@ def cli():
logo()
#==============================================================================
# Command: version
#==============================================================================
@cli.command("version", help="Show the currently installed version of MVT")
def version():
return
#==============================================================================
# Command: decrypt-backup
#==============================================================================

View File

@@ -7,10 +7,12 @@ from .cache_files import CacheFiles
from .filesystem import Filesystem
from .net_netusage import Netusage
from .safari_favicon import SafariFavicon
from .shutdownlog import ShutdownLog
from .version_history import IOSVersionHistory
from .webkit_indexeddb import WebkitIndexedDB
from .webkit_localstorage import WebkitLocalStorage
from .webkit_safariviewservice import WebkitSafariViewService
FS_MODULES = [CacheFiles, Filesystem, Netusage, SafariFavicon, IOSVersionHistory,
WebkitIndexedDB, WebkitLocalStorage, WebkitSafariViewService,]
FS_MODULES = [CacheFiles, Filesystem, Netusage, SafariFavicon, ShutdownLog,
IOSVersionHistory, WebkitIndexedDB, WebkitLocalStorage,
WebkitSafariViewService,]

View File

@@ -0,0 +1,81 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 The MVT Project Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from ..base import IOSExtraction
SHUTDOWN_LOG_PATH = [
"private/var/db/diagnostics/shutdown.log",
]
class ShutdownLog(IOSExtraction):
"""This module extracts processes information from the shutdown log file."""
def __init__(self, file_path=None, base_folder=None, output_folder=None,
fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results)
def serialize(self, record):
return {
"timestamp": record["isodate"],
"module": self.__class__.__name__,
"event": "shutdown",
"data": f"Client {record['client']} with PID {record['pid']} was running when the device was shut down",
}
def check_indicators(self):
if not self.indicators:
return
for result in self.results:
for ioc in self.indicators.ioc_processes:
parts = result["client"].split("/")
if ioc in parts:
self.log.warning("Found mention of a known malicious process \"%s\" in shutdown.log",
ioc)
self.detected.append(result)
def process_shutdownlog(self, content):
current_processes = []
for line in content.split("\n"):
line = line.strip()
if line.startswith("remaining client pid:"):
current_processes.append({
"pid": line[line.find("pid: ")+5:line.find(" (")],
"client": line[line.find("(")+1:line.find(")")],
})
elif line.startswith("SIGTERM: "):
try:
mac_timestamp = int(line[line.find("[")+1:line.find("]")])
except ValueError:
try:
start = line.find(" @")+2
mac_timestamp = int(line[start:start+10])
except:
mac_timestamp = 0
timestamp = convert_mactime_to_unix(mac_timestamp, from_2001=False)
isodate = convert_timestamp_to_iso(timestamp)
for current_process in current_processes:
self.results.append({
"isodate": isodate,
"pid": current_process["pid"],
"client": current_process["client"],
})
current_processes = []
self.results = sorted(self.results, key=lambda entry: entry["isodate"])
def run(self):
self._find_ios_database(root_paths=SHUTDOWN_LOG_PATH)
self.log.info("Found shutdown log at path: %s", self.file_path)
with open(self.file_path, "r") as handle:
self.process_shutdownlog(handle.read())

View File

@@ -223,6 +223,7 @@ IPHONE_IOS_VERSIONS = [
{"build": "18G69", "version": "14.7"},
{"build": "18G82", "version": "14.7.1"},
{"build": "18H17", "version": "14.8"},
{"build": "19A346", "version": "15.0"},
]
def get_device_desc_from_id(identifier, devices_list=IPHONE_MODELS):