mirror of https://github.com/mvt-project/mvt.git
Standardized code with flake8
This commit is contained in:
parent
b3a464ba58
commit
512f40dcb4
|
@ -9,7 +9,9 @@ import os
|
||||||
import click
|
import click
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
|
|
||||||
from mvt.common.help import *
|
from mvt.common.help import HELP_MSG_MODULE, HELP_MSG_IOC
|
||||||
|
from mvt.common.help import HELP_MSG_OUTPUT, HELP_MSG_LIST_MODULES
|
||||||
|
from mvt.common.help import HELP_MSG_SERIAL
|
||||||
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
|
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
|
||||||
from mvt.common.logo import logo
|
from mvt.common.logo import logo
|
||||||
from mvt.common.module import run_module, save_timeline
|
from mvt.common.module import run_module, save_timeline
|
||||||
|
@ -26,6 +28,7 @@ logging.basicConfig(level="INFO", format=LOG_FORMAT, handlers=[
|
||||||
RichHandler(show_path=False, log_time_format="%X")])
|
RichHandler(show_path=False, log_time_format="%X")])
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Main
|
# Main
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
@ -191,7 +194,7 @@ def check_backup(ctx, iocs, output, backup_path, serial):
|
||||||
log.critical("The path you specified is a not a folder!")
|
log.critical("The path you specified is a not a folder!")
|
||||||
|
|
||||||
if os.path.basename(backup_path) == "backup.ab":
|
if os.path.basename(backup_path) == "backup.ab":
|
||||||
log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) " \
|
log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) "
|
||||||
"to extract 'backup.ab' files!")
|
"to extract 'backup.ab' files!")
|
||||||
ctx.exit(1)
|
ctx.exit(1)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ from .modules.adb.packages import Packages
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Would be better to replace tqdm with rich.progress to reduce
|
# TODO: Would be better to replace tqdm with rich.progress to reduce
|
||||||
# the number of dependencies. Need to investigate whether
|
# the number of dependencies. Need to investigate whether
|
||||||
# it's possible to have a similar callback system.
|
# it's possible to have a similar callback system.
|
||||||
|
@ -137,7 +138,7 @@ class DownloadAPKs(AndroidExtraction):
|
||||||
packages_selection.append(package)
|
packages_selection.append(package)
|
||||||
|
|
||||||
log.info("Selected only %d packages which are not marked as system",
|
log.info("Selected only %d packages which are not marked as system",
|
||||||
len(packages_selection))
|
len(packages_selection))
|
||||||
|
|
||||||
if len(packages_selection) == 0:
|
if len(packages_selection) == 0:
|
||||||
log.info("No packages were selected for download")
|
log.info("No packages were selected for download")
|
||||||
|
|
|
@ -13,6 +13,7 @@ from rich.text import Text
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def koodous_lookup(packages):
|
def koodous_lookup(packages):
|
||||||
log.info("Looking up all extracted files on Koodous (www.koodous.com)")
|
log.info("Looking up all extracted files on Koodous (www.koodous.com)")
|
||||||
log.info("This might take a while...")
|
log.info("This might take a while...")
|
||||||
|
|
|
@ -13,6 +13,7 @@ from rich.text import Text
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_virustotal_report(hashes):
|
def get_virustotal_report(hashes):
|
||||||
apikey = "233f22e200ca5822bd91103043ccac138b910db79f29af5616a9afe8b6f215ad"
|
apikey = "233f22e200ca5822bd91103043ccac138b910db79f29af5616a9afe8b6f215ad"
|
||||||
url = f"https://www.virustotal.com/partners/sysinternals/file-reports?apikey={apikey}"
|
url = f"https://www.virustotal.com/partners/sysinternals/file-reports?apikey={apikey}"
|
||||||
|
@ -36,6 +37,7 @@ def get_virustotal_report(hashes):
|
||||||
log.error("Unexpected response from VirusTotal: %s", res.status_code)
|
log.error("Unexpected response from VirusTotal: %s", res.status_code)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def virustotal_lookup(packages):
|
def virustotal_lookup(packages):
|
||||||
log.info("Looking up all extracted files on VirusTotal (www.virustotal.com)")
|
log.info("Looking up all extracted files on VirusTotal (www.virustotal.com)")
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@ def virustotal_lookup(packages):
|
||||||
total_unique_hashes = len(unique_hashes)
|
total_unique_hashes = len(unique_hashes)
|
||||||
|
|
||||||
detections = {}
|
detections = {}
|
||||||
|
|
||||||
def virustotal_query(batch):
|
def virustotal_query(batch):
|
||||||
report = get_virustotal_report(batch)
|
report = get_virustotal_report(batch)
|
||||||
if not report:
|
if not report:
|
||||||
|
|
|
@ -25,6 +25,7 @@ log = logging.getLogger(__name__)
|
||||||
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
|
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
|
||||||
ADB_PUB_KEY_PATH = os.path.expanduser("~/.android/adbkey.pub")
|
ADB_PUB_KEY_PATH = os.path.expanduser("~/.android/adbkey.pub")
|
||||||
|
|
||||||
|
|
||||||
class AndroidExtraction(MVTModule):
|
class AndroidExtraction(MVTModule):
|
||||||
"""This class provides a base for all Android extraction modules."""
|
"""This class provides a base for all Android extraction modules."""
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ class AndroidExtraction(MVTModule):
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == 113 and self.serial:
|
if e.errno == 113 and self.serial:
|
||||||
log.critical("Unable to connect to the device %s: did you specify the correct IP addres?",
|
log.critical("Unable to connect to the device %s: did you specify the correct IP addres?",
|
||||||
self.serial)
|
self.serial)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
|
@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
CHROME_HISTORY_PATH = "data/data/com.android.chrome/app_chrome/Default/History"
|
CHROME_HISTORY_PATH = "data/data/com.android.chrome/app_chrome/Default/History"
|
||||||
|
|
||||||
|
|
||||||
class ChromeHistory(AndroidExtraction):
|
class ChromeHistory(AndroidExtraction):
|
||||||
"""This module extracts records from Android's Chrome browsing history."""
|
"""This module extracts records from Android's Chrome browsing history."""
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DumpsysBatterystats(AndroidExtraction):
|
class DumpsysBatterystats(AndroidExtraction):
|
||||||
"""This module extracts stats on battery consumption by processes."""
|
"""This module extracts stats on battery consumption by processes."""
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ class DumpsysBatterystats(AndroidExtraction):
|
||||||
handle.write(stats)
|
handle.write(stats)
|
||||||
|
|
||||||
log.info("Records from dumpsys batterystats stored at %s",
|
log.info("Records from dumpsys batterystats stored at %s",
|
||||||
stats_path)
|
stats_path)
|
||||||
|
|
||||||
history = self._adb_command("dumpsys batterystats --history")
|
history = self._adb_command("dumpsys batterystats --history")
|
||||||
if self.output_folder:
|
if self.output_folder:
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DumpsysFull(AndroidExtraction):
|
class DumpsysFull(AndroidExtraction):
|
||||||
"""This module extracts stats on battery consumption by processes."""
|
"""This module extracts stats on battery consumption by processes."""
|
||||||
|
|
||||||
|
@ -30,6 +31,6 @@ class DumpsysFull(AndroidExtraction):
|
||||||
handle.write(stats)
|
handle.write(stats)
|
||||||
|
|
||||||
log.info("Full dumpsys output stored at %s",
|
log.info("Full dumpsys output stored at %s",
|
||||||
stats_path)
|
stats_path)
|
||||||
|
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DumpsysProcstats(AndroidExtraction):
|
class DumpsysProcstats(AndroidExtraction):
|
||||||
"""This module extracts stats on memory consumption by processes."""
|
"""This module extracts stats on memory consumption by processes."""
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
|
||||||
ACTION_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
|
ACTION_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
|
||||||
ACTION_PHONE_STATE = "android.intent.action.PHONE_STATE"
|
ACTION_PHONE_STATE = "android.intent.action.PHONE_STATE"
|
||||||
|
|
||||||
|
|
||||||
class DumpsysReceivers(AndroidExtraction):
|
class DumpsysReceivers(AndroidExtraction):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
|
@ -66,16 +67,16 @@ class DumpsysReceivers(AndroidExtraction):
|
||||||
|
|
||||||
if activity == ACTION_NEW_OUTGOING_SMS:
|
if activity == ACTION_NEW_OUTGOING_SMS:
|
||||||
self.log.info("Found a receiver to intercept outgoing SMS messages: \"%s\"",
|
self.log.info("Found a receiver to intercept outgoing SMS messages: \"%s\"",
|
||||||
receiver)
|
receiver)
|
||||||
elif activity == ACTION_SMS_RECEIVED:
|
elif activity == ACTION_SMS_RECEIVED:
|
||||||
self.log.info("Found a receiver to intercept incoming SMS messages: \"%s\"",
|
self.log.info("Found a receiver to intercept incoming SMS messages: \"%s\"",
|
||||||
receiver)
|
receiver)
|
||||||
elif activity == ACTION_DATA_SMS_RECEIVED:
|
elif activity == ACTION_DATA_SMS_RECEIVED:
|
||||||
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
|
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
|
||||||
receiver)
|
receiver)
|
||||||
elif activity == ACTION_PHONE_STATE:
|
elif activity == ACTION_PHONE_STATE:
|
||||||
self.log.info("Found a receiver monitoring telephony state: \"%s\"",
|
self.log.info("Found a receiver monitoring telephony state: \"%s\"",
|
||||||
receiver)
|
receiver)
|
||||||
|
|
||||||
self.results.append({
|
self.results.append({
|
||||||
"activity": activity,
|
"activity": activity,
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Files(AndroidExtraction):
|
class Files(AndroidExtraction):
|
||||||
"""This module extracts the list of installed packages."""
|
"""This module extracts the list of installed packages."""
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Packages(AndroidExtraction):
|
class Packages(AndroidExtraction):
|
||||||
"""This module extracts the list of installed packages."""
|
"""This module extracts the list of installed packages."""
|
||||||
|
|
||||||
|
@ -49,11 +50,10 @@ class Packages(AndroidExtraction):
|
||||||
root_packages = root_packages_string.decode("utf-8").split("\n")
|
root_packages = root_packages_string.decode("utf-8").split("\n")
|
||||||
root_packages = [rp.strip() for rp in root_packages]
|
root_packages = [rp.strip() for rp in root_packages]
|
||||||
|
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if result["package_name"] in root_packages:
|
if result["package_name"] in root_packages:
|
||||||
self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"",
|
self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"",
|
||||||
result["package_name"])
|
result["package_name"])
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
if result["package_name"] in self.indicators.ioc_app_ids:
|
if result["package_name"] in self.indicators.ioc_app_ids:
|
||||||
self.log.warning("Found a malicious package name: \"%s\"",
|
self.log.warning("Found a malicious package name: \"%s\"",
|
||||||
|
|
|
@ -9,6 +9,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Processes(AndroidExtraction):
|
class Processes(AndroidExtraction):
|
||||||
"""This module extracts details on running processes."""
|
"""This module extracts details on running processes."""
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RootBinaries(AndroidExtraction):
|
class RootBinaries(AndroidExtraction):
|
||||||
"""This module extracts the list of installed packages."""
|
"""This module extracts the list of installed packages."""
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,12 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
|
SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
|
||||||
SMS_BUGLE_QUERY = """
|
SMS_BUGLE_QUERY = """
|
||||||
SELECT
|
SELECT
|
||||||
ppl.normalized_destination AS number,
|
ppl.normalized_destination AS number,
|
||||||
p.timestamp AS timestamp,
|
p.timestamp AS timestamp,
|
||||||
CASE WHEN m.sender_id IN
|
CASE WHEN m.sender_id IN
|
||||||
(SELECT _id FROM participants WHERE contact_id=-1)
|
(SELECT _id FROM participants WHERE contact_id=-1)
|
||||||
THEN 2 ELSE 1 END incoming, p.text AS text
|
THEN 2 ELSE 1 END incoming, p.text AS text
|
||||||
FROM messages m, conversations c, parts p,
|
FROM messages m, conversations c, parts p,
|
||||||
participants ppl, conversation_participants cp
|
participants ppl, conversation_participants cp
|
||||||
WHERE (m.conversation_id = c._id)
|
WHERE (m.conversation_id = c._id)
|
||||||
|
@ -31,14 +31,15 @@ WHERE (m.conversation_id = c._id)
|
||||||
|
|
||||||
SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db"
|
SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db"
|
||||||
SMS_MMSMS_QUERY = """
|
SMS_MMSMS_QUERY = """
|
||||||
SELECT
|
SELECT
|
||||||
address AS number,
|
address AS number,
|
||||||
date_sent AS timestamp,
|
date_sent AS timestamp,
|
||||||
type as incoming,
|
type as incoming,
|
||||||
body AS text
|
body AS text
|
||||||
FROM sms;
|
FROM sms;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class SMS(AndroidExtraction):
|
class SMS(AndroidExtraction):
|
||||||
"""This module extracts all SMS messages containing links."""
|
"""This module extracts all SMS messages containing links."""
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class SMS(AndroidExtraction):
|
||||||
return
|
return
|
||||||
|
|
||||||
for message in self.results:
|
for message in self.results:
|
||||||
if not "text" in message:
|
if "text" not in message:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
message_links = check_for_links(message["text"])
|
message_links = check_for_links(message["text"])
|
||||||
|
@ -77,7 +78,7 @@ class SMS(AndroidExtraction):
|
||||||
"""
|
"""
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
if (self.SMS_DB_TYPE == 1):
|
if (self.SMS_DB_TYPE == 1):
|
||||||
cur.execute(SMS_BUGLE_QUERY)
|
cur.execute(SMS_BUGLE_QUERY)
|
||||||
elif (self.SMS_DB_TYPE == 2):
|
elif (self.SMS_DB_TYPE == 2):
|
||||||
|
|
|
@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
WHATSAPP_PATH = "data/data/com.whatsapp/databases/msgstore.db"
|
WHATSAPP_PATH = "data/data/com.whatsapp/databases/msgstore.db"
|
||||||
|
|
||||||
|
|
||||||
class Whatsapp(AndroidExtraction):
|
class Whatsapp(AndroidExtraction):
|
||||||
"""This module extracts all WhatsApp messages containing links."""
|
"""This module extracts all WhatsApp messages containing links."""
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ class Whatsapp(AndroidExtraction):
|
||||||
return
|
return
|
||||||
|
|
||||||
for message in self.results:
|
for message in self.results:
|
||||||
if not "data" in message:
|
if "data" not in message:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
message_links = check_for_links(message["data"])
|
message_links = check_for_links(message["data"])
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
|
|
||||||
from .sms import SMS
|
from .sms import SMS
|
||||||
|
|
||||||
BACKUP_MODULES = [SMS,]
|
BACKUP_MODULES = [SMS]
|
||||||
|
|
|
@ -24,7 +24,7 @@ class SMS(MVTModule):
|
||||||
return
|
return
|
||||||
|
|
||||||
for message in self.results:
|
for message in self.results:
|
||||||
if not "body" in message:
|
if "body" not in message:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
message_links = check_for_links(message["body"])
|
message_links = check_for_links(message["body"])
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .url import URL
|
||||||
class IndicatorsFileBadFormat(Exception):
|
class IndicatorsFileBadFormat(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Indicators:
|
class Indicators:
|
||||||
"""This class is used to parse indicators from a STIX2 file and provide
|
"""This class is used to parse indicators from a STIX2 file and provide
|
||||||
functions to compare extracted artifacts to the indicators.
|
functions to compare extracted artifacts to the indicators.
|
||||||
|
@ -115,7 +116,7 @@ class Indicators:
|
||||||
else:
|
else:
|
||||||
# If it's not shortened, we just use the original URL object.
|
# If it's not shortened, we just use the original URL object.
|
||||||
final_url = orig_url
|
final_url = orig_url
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# If URL parsing failed, we just try to do a simple substring
|
# If URL parsing failed, we just try to do a simple substring
|
||||||
# match.
|
# match.
|
||||||
for ioc in self.ioc_domains:
|
for ioc in self.ioc_domains:
|
||||||
|
|
|
@ -16,7 +16,7 @@ def logo():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
latest_version = check_for_updates()
|
latest_version = check_for_updates()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if latest_version:
|
if latest_version:
|
||||||
|
|
|
@ -14,12 +14,15 @@ import simplejson as json
|
||||||
class DatabaseNotFoundError(Exception):
|
class DatabaseNotFoundError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DatabaseCorruptedError(Exception):
|
class DatabaseCorruptedError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InsufficientPrivileges(Exception):
|
class InsufficientPrivileges(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MVTModule(object):
|
class MVTModule(object):
|
||||||
"""This class provides a base for all extraction modules."""
|
"""This class provides a base for all extraction modules."""
|
||||||
|
|
||||||
|
|
|
@ -250,6 +250,7 @@ SHORTENER_DOMAINS = [
|
||||||
"zz.gd",
|
"zz.gd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class URL:
|
class URL:
|
||||||
|
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
|
@ -273,7 +274,7 @@ class URL:
|
||||||
# TODO: Properly handle exception.
|
# TODO: Properly handle exception.
|
||||||
try:
|
try:
|
||||||
return get_tld(self.url, as_object=True, fix_protocol=True).parsed_url.netloc.lower().lstrip("www.")
|
return get_tld(self.url, as_object=True, fix_protocol=True).parsed_url.netloc.lower().lstrip("www.")
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_top_level(self):
|
def get_top_level(self):
|
||||||
|
@ -288,7 +289,7 @@ class URL:
|
||||||
# TODO: Properly handle exception.
|
# TODO: Properly handle exception.
|
||||||
try:
|
try:
|
||||||
return get_tld(self.url, as_object=True, fix_protocol=True).fld.lower()
|
return get_tld(self.url, as_object=True, fix_protocol=True).fld.lower()
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def check_if_shortened(self) -> bool:
|
def check_if_shortened(self) -> bool:
|
||||||
|
|
|
@ -45,7 +45,7 @@ def convert_chrometime_to_unix(timestamp):
|
||||||
:returns: Unix epoch timestamp.
|
:returns: Unix epoch timestamp.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
epoch_start = datetime.datetime(1601, 1 , 1)
|
epoch_start = datetime.datetime(1601, 1, 1)
|
||||||
delta = datetime.timedelta(microseconds=timestamp)
|
delta = datetime.timedelta(microseconds=timestamp)
|
||||||
return epoch_start + delta
|
return epoch_start + delta
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ def convert_timestamp_to_iso(timestamp):
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_for_links(text):
|
def check_for_links(text):
|
||||||
"""Checks if a given text contains HTTP links.
|
"""Checks if a given text contains HTTP links.
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ def check_for_links(text):
|
||||||
"""
|
"""
|
||||||
return re.findall("(?P<url>https?://[^\s]+)", text, re.IGNORECASE)
|
return re.findall("(?P<url>https?://[^\s]+)", text, re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
def get_sha256_from_file_path(file_path):
|
def get_sha256_from_file_path(file_path):
|
||||||
"""Calculate the SHA256 hash of a file from a file path.
|
"""Calculate the SHA256 hash of a file from a file path.
|
||||||
|
|
||||||
|
@ -88,6 +90,7 @@ def get_sha256_from_file_path(file_path):
|
||||||
|
|
||||||
return sha256_hash.hexdigest()
|
return sha256_hash.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
# Note: taken from here:
|
# Note: taken from here:
|
||||||
# https://stackoverflow.com/questions/57014259/json-dumps-on-dictionary-with-bytes-for-keys
|
# https://stackoverflow.com/questions/57014259/json-dumps-on-dictionary-with-bytes-for-keys
|
||||||
def keys_bytes_to_string(obj):
|
def keys_bytes_to_string(obj):
|
||||||
|
|
|
@ -8,6 +8,7 @@ from packaging import version
|
||||||
|
|
||||||
MVT_VERSION = "1.2.14"
|
MVT_VERSION = "1.2.14"
|
||||||
|
|
||||||
|
|
||||||
def check_for_updates():
|
def check_for_updates():
|
||||||
res = requests.get("https://pypi.org/pypi/mvt/json")
|
res = requests.get("https://pypi.org/pypi/mvt/json")
|
||||||
data = res.json()
|
data = res.json()
|
||||||
|
|
|
@ -10,7 +10,9 @@ import click
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
from rich.prompt import Prompt
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
from mvt.common.help import *
|
from mvt.common.help import HELP_MSG_MODULE, HELP_MSG_IOC
|
||||||
|
from mvt.common.help import HELP_MSG_FAST, HELP_MSG_OUTPUT
|
||||||
|
from mvt.common.help import HELP_MSG_LIST_MODULES
|
||||||
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
|
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
|
||||||
from mvt.common.logo import logo
|
from mvt.common.logo import logo
|
||||||
from mvt.common.module import run_module, save_timeline
|
from mvt.common.module import run_module, save_timeline
|
||||||
|
@ -30,6 +32,7 @@ log = logging.getLogger(__name__)
|
||||||
# Set this environment variable to a password if needed.
|
# Set this environment variable to a password if needed.
|
||||||
PASSWD_ENV = "MVT_IOS_BACKUP_PASSWORD"
|
PASSWD_ENV = "MVT_IOS_BACKUP_PASSWORD"
|
||||||
|
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Main
|
# Main
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|
|
@ -14,6 +14,7 @@ from iOSbackup import iOSbackup
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DecryptBackup:
|
class DecryptBackup:
|
||||||
"""This class provides functions to decrypt an encrypted iTunes backup
|
"""This class provides functions to decrypt an encrypted iTunes backup
|
||||||
using either a password or a key file.
|
using either a password or a key file.
|
||||||
|
|
|
@ -10,6 +10,7 @@ from ..base import IOSExtraction
|
||||||
|
|
||||||
CONF_PROFILES_DOMAIN = "SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles"
|
CONF_PROFILES_DOMAIN = "SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles"
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationProfiles(IOSExtraction):
|
class ConfigurationProfiles(IOSExtraction):
|
||||||
"""This module extracts the full plist data from configuration profiles."""
|
"""This module extracts the full plist data from configuration profiles."""
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ class Manifest(IOSExtraction):
|
||||||
"""Unserialized plist objects can have keys which are str or byte types
|
"""Unserialized plist objects can have keys which are str or byte types
|
||||||
This is a helper to try fetch a key as both a byte or string type.
|
This is a helper to try fetch a key as both a byte or string type.
|
||||||
|
|
||||||
:param dictionary: param key:
|
:param dictionary:
|
||||||
:param key:
|
:param key:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return dictionary.get(key.encode("utf-8"), None) or dictionary.get(key, None)
|
return dictionary.get(key.encode("utf-8"), None) or dictionary.get(key, None)
|
||||||
|
@ -38,7 +38,7 @@ class Manifest(IOSExtraction):
|
||||||
def _convert_timestamp(timestamp_or_unix_time_int):
|
def _convert_timestamp(timestamp_or_unix_time_int):
|
||||||
"""Older iOS versions stored the manifest times as unix timestamps.
|
"""Older iOS versions stored the manifest times as unix timestamps.
|
||||||
|
|
||||||
:param timestamp_or_unix_time_int:
|
:param timestamp_or_unix_time_int:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(timestamp_or_unix_time_int, datetime.datetime):
|
if isinstance(timestamp_or_unix_time_int, datetime.datetime):
|
||||||
|
@ -72,7 +72,7 @@ class Manifest(IOSExtraction):
|
||||||
return
|
return
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if not "relative_path" in result:
|
if "relative_path" not in result:
|
||||||
continue
|
continue
|
||||||
if not result["relative_path"]:
|
if not result["relative_path"]:
|
||||||
continue
|
continue
|
||||||
|
@ -133,7 +133,7 @@ class Manifest(IOSExtraction):
|
||||||
"owner": self._get_key(file_metadata, "UserID"),
|
"owner": self._get_key(file_metadata, "UserID"),
|
||||||
"size": self._get_key(file_metadata, "Size"),
|
"size": self._get_key(file_metadata, "Size"),
|
||||||
})
|
})
|
||||||
except:
|
except Exception:
|
||||||
self.log.exception("Error reading manifest file metadata for file with ID %s and relative path %s",
|
self.log.exception("Error reading manifest file metadata for file with ID %s and relative path %s",
|
||||||
file_data["fileID"], file_data["relativePath"])
|
file_data["fileID"], file_data["relativePath"])
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -11,6 +11,7 @@ from ..base import IOSExtraction
|
||||||
|
|
||||||
CONF_PROFILES_EVENTS_RELPATH = "Library/ConfigurationProfiles/MCProfileEvents.plist"
|
CONF_PROFILES_EVENTS_RELPATH = "Library/ConfigurationProfiles/MCProfileEvents.plist"
|
||||||
|
|
||||||
|
|
||||||
class ProfileEvents(IOSExtraction):
|
class ProfileEvents(IOSExtraction):
|
||||||
"""This module extracts events related to the installation of configuration
|
"""This module extracts events related to the installation of configuration
|
||||||
profiles.
|
profiles.
|
||||||
|
|
|
@ -16,4 +16,4 @@ from .webkit_safariviewservice import WebkitSafariViewService
|
||||||
|
|
||||||
FS_MODULES = [CacheFiles, Filesystem, Netusage, Analytics, SafariFavicon, ShutdownLog,
|
FS_MODULES = [CacheFiles, Filesystem, Netusage, Analytics, SafariFavicon, ShutdownLog,
|
||||||
IOSVersionHistory, WebkitIndexedDB, WebkitLocalStorage,
|
IOSVersionHistory, WebkitIndexedDB, WebkitLocalStorage,
|
||||||
WebkitSafariViewService,]
|
WebkitSafariViewService]
|
||||||
|
|
|
@ -14,6 +14,7 @@ ANALYTICS_DB_PATH = [
|
||||||
"private/var/Keychains/Analytics/*.db",
|
"private/var/Keychains/Analytics/*.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Analytics(IOSExtraction):
|
class Analytics(IOSExtraction):
|
||||||
"""This module extracts information from the private/var/Keychains/Analytics/*.db files."""
|
"""This module extracts information from the private/var/Keychains/Analytics/*.db files."""
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ class Analytics(IOSExtraction):
|
||||||
"event": record["artifact"],
|
"event": record["artifact"],
|
||||||
"data": f"{record}",
|
"data": f"{record}",
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self):
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
@ -50,7 +51,7 @@ class Analytics(IOSExtraction):
|
||||||
ioc, result["artifact"], result["timestamp"])
|
ioc, result["artifact"], result["timestamp"])
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
break
|
break
|
||||||
|
|
||||||
def _extract_analytics_data(self):
|
def _extract_analytics_data(self):
|
||||||
artifact = self.file_path.split("/")[-1]
|
artifact = self.file_path.split("/")[-1]
|
||||||
|
|
||||||
|
@ -87,7 +88,6 @@ class Analytics(IOSExtraction):
|
||||||
FROM soft_failures;
|
FROM soft_failures;
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
for row in cur:
|
for row in cur:
|
||||||
if row[0] and row[1]:
|
if row[0] and row[1]:
|
||||||
timestamp = convert_timestamp_to_iso(convert_mactime_to_unix(row[0], False))
|
timestamp = convert_timestamp_to_iso(convert_mactime_to_unix(row[0], False))
|
||||||
|
|
|
@ -38,7 +38,7 @@ class CacheFiles(IOSExtraction):
|
||||||
for item in items:
|
for item in items:
|
||||||
if self.indicators.check_domain(item["url"]):
|
if self.indicators.check_domain(item["url"]):
|
||||||
if key not in self.detected:
|
if key not in self.detected:
|
||||||
self.detected[key] = [item,]
|
self.detected[key] = [item, ]
|
||||||
else:
|
else:
|
||||||
self.detected[key].append(item)
|
self.detected[key].append(item)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class CacheFiles(IOSExtraction):
|
||||||
return
|
return
|
||||||
|
|
||||||
key_name = os.path.relpath(file_path, self.base_folder)
|
key_name = os.path.relpath(file_path, self.base_folder)
|
||||||
if not key_name in self.results:
|
if key_name not in self.results:
|
||||||
self.results[key_name] = []
|
self.results[key_name] = []
|
||||||
|
|
||||||
for row in cur:
|
for row in cur:
|
||||||
|
|
|
@ -60,7 +60,7 @@ class Filesystem(IOSExtraction):
|
||||||
"path": os.path.relpath(dir_path, self.base_folder),
|
"path": os.path.relpath(dir_path, self.base_folder),
|
||||||
"modified": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(dir_path).st_mtime)),
|
"modified": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(dir_path).st_mtime)),
|
||||||
}
|
}
|
||||||
except:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.results.append(result)
|
self.results.append(result)
|
||||||
|
@ -72,7 +72,7 @@ class Filesystem(IOSExtraction):
|
||||||
"path": os.path.relpath(file_path, self.base_folder),
|
"path": os.path.relpath(file_path, self.base_folder),
|
||||||
"modified": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(file_path).st_mtime)),
|
"modified": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(file_path).st_mtime)),
|
||||||
}
|
}
|
||||||
except:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.results.append(result)
|
self.results.append(result)
|
||||||
|
|
|
@ -12,6 +12,7 @@ NETUSAGE_ROOT_PATHS = [
|
||||||
"private/var/networkd/db/netusage.sqlite"
|
"private/var/networkd/db/netusage.sqlite"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Netusage(NetBase):
|
class Netusage(NetBase):
|
||||||
"""This class extracts data from netusage.sqlite and attempts to identify
|
"""This class extracts data from netusage.sqlite and attempts to identify
|
||||||
any suspicious processes if running on a full filesystem dump.
|
any suspicious processes if running on a full filesystem dump.
|
||||||
|
|
|
@ -14,6 +14,7 @@ SAFARI_FAVICON_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/Image Cache/Favicons/Favicons.db",
|
"private/var/mobile/Containers/Data/Application/*/Library/Image Cache/Favicons/Favicons.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SafariFavicon(IOSExtraction):
|
class SafariFavicon(IOSExtraction):
|
||||||
"""This module extracts all Safari favicon records."""
|
"""This module extracts all Safari favicon records."""
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ SHUTDOWN_LOG_PATH = [
|
||||||
"private/var/db/diagnostics/shutdown.log",
|
"private/var/db/diagnostics/shutdown.log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ShutdownLog(IOSExtraction):
|
class ShutdownLog(IOSExtraction):
|
||||||
"""This module extracts processes information from the shutdown log file."""
|
"""This module extracts processes information from the shutdown log file."""
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ class ShutdownLog(IOSExtraction):
|
||||||
"event": "shutdown",
|
"event": "shutdown",
|
||||||
"data": f"Client {record['client']} with PID {record['pid']} was running when the device was shut down",
|
"data": f"Client {record['client']} with PID {record['pid']} was running when the device was shut down",
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self):
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
@ -57,7 +58,7 @@ class ShutdownLog(IOSExtraction):
|
||||||
try:
|
try:
|
||||||
start = line.find(" @")+2
|
start = line.find(" @")+2
|
||||||
mac_timestamp = int(line[start:start+10])
|
mac_timestamp = int(line[start:start+10])
|
||||||
except:
|
except Exception:
|
||||||
mac_timestamp = 0
|
mac_timestamp = 0
|
||||||
|
|
||||||
timestamp = convert_mactime_to_unix(mac_timestamp, from_2001=False)
|
timestamp = convert_mactime_to_unix(mac_timestamp, from_2001=False)
|
||||||
|
|
|
@ -14,6 +14,7 @@ IOS_ANALYTICS_JOURNAL_PATHS = [
|
||||||
"private/var/db/analyticsd/Analytics-Journal-*.ips",
|
"private/var/db/analyticsd/Analytics-Journal-*.ips",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IOSVersionHistory(IOSExtraction):
|
class IOSVersionHistory(IOSExtraction):
|
||||||
"""This module extracts iOS update history from Analytics Journal log files."""
|
"""This module extracts iOS update history from Analytics Journal log files."""
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ WEBKIT_INDEXEDDB_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/IndexedDB",
|
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/IndexedDB",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WebkitIndexedDB(WebkitBase):
|
class WebkitIndexedDB(WebkitBase):
|
||||||
"""This module looks extracts records from WebKit IndexedDB folders,
|
"""This module looks extracts records from WebKit IndexedDB folders,
|
||||||
and checks them against any provided list of suspicious domains.
|
and checks them against any provided list of suspicious domains.
|
||||||
|
|
|
@ -9,6 +9,7 @@ WEBKIT_LOCALSTORAGE_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/LocalStorage/",
|
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/LocalStorage/",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WebkitLocalStorage(WebkitBase):
|
class WebkitLocalStorage(WebkitBase):
|
||||||
"""This module looks extracts records from WebKit LocalStorage folders,
|
"""This module looks extracts records from WebKit LocalStorage folders,
|
||||||
and checks them against any provided list of suspicious domains.
|
and checks them against any provided list of suspicious domains.
|
||||||
|
|
|
@ -9,6 +9,7 @@ WEBKIT_SAFARIVIEWSERVICE_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/",
|
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WebkitSafariViewService(WebkitBase):
|
class WebkitSafariViewService(WebkitBase):
|
||||||
"""This module looks extracts records from WebKit LocalStorage folders,
|
"""This module looks extracts records from WebKit LocalStorage folders,
|
||||||
and checks them against any provided list of suspicious domains.
|
and checks them against any provided list of suspicious domains.
|
||||||
|
|
|
@ -27,4 +27,4 @@ MIXED_MODULES = [Calls, ChromeFavicon, ChromeHistory, Contacts, FirefoxFavicon,
|
||||||
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
||||||
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
|
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
|
||||||
TCC, SMS, SMSAttachments, WebkitResourceLoadStatistics,
|
TCC, SMS, SMSAttachments, WebkitResourceLoadStatistics,
|
||||||
WebkitSessionResourceLog, Whatsapp,]
|
WebkitSessionResourceLog, Whatsapp]
|
||||||
|
|
|
@ -16,6 +16,7 @@ CALLS_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/CallHistoryDB/CallHistory.storedata"
|
"private/var/mobile/Library/CallHistoryDB/CallHistory.storedata"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Calls(IOSExtraction):
|
class Calls(IOSExtraction):
|
||||||
"""This module extracts phone calls details"""
|
"""This module extracts phone calls details"""
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ class Calls(IOSExtraction):
|
||||||
ZDATE, ZDURATION, ZLOCATION, ZADDRESS, ZSERVICE_PROVIDER
|
ZDATE, ZDURATION, ZLOCATION, ZADDRESS, ZSERVICE_PROVIDER
|
||||||
FROM ZCALLRECORD;
|
FROM ZCALLRECORD;
|
||||||
""")
|
""")
|
||||||
names = [description[0] for description in cur.description]
|
# names = [description[0] for description in cur.description]
|
||||||
|
|
||||||
for row in cur:
|
for row in cur:
|
||||||
self.results.append({
|
self.results.append({
|
||||||
|
|
|
@ -19,6 +19,7 @@ CHROME_FAVICON_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/Application Support/Google/Chrome/Default/Favicons",
|
"private/var/mobile/Containers/Data/Application/*/Library/Application Support/Google/Chrome/Default/Favicons",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ChromeFavicon(IOSExtraction):
|
class ChromeFavicon(IOSExtraction):
|
||||||
"""This module extracts all Chrome favicon records."""
|
"""This module extracts all Chrome favicon records."""
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@ from ..base import IOSExtraction
|
||||||
CHROME_HISTORY_BACKUP_IDS = [
|
CHROME_HISTORY_BACKUP_IDS = [
|
||||||
"faf971ce92c3ac508c018dce1bef2a8b8e9838f1",
|
"faf971ce92c3ac508c018dce1bef2a8b8e9838f1",
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: Confirm Chrome database path.
|
# TODO: Confirm Chrome database path.
|
||||||
CHROME_HISTORY_ROOT_PATHS = [
|
CHROME_HISTORY_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/Application Support/Google/Chrome/Default/History",
|
"private/var/mobile/Containers/Data/Application/*/Library/Application Support/Google/Chrome/Default/History",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ChromeHistory(IOSExtraction):
|
class ChromeHistory(IOSExtraction):
|
||||||
"""This module extracts all Chome visits."""
|
"""This module extracts all Chome visits."""
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ CONTACTS_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/AddressBook/AddressBook.sqlitedb",
|
"private/var/mobile/Library/AddressBook/AddressBook.sqlitedb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Contacts(IOSExtraction):
|
class Contacts(IOSExtraction):
|
||||||
"""This module extracts all contact details from the phone's address book."""
|
"""This module extracts all contact details from the phone's address book."""
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ FIREFOX_HISTORY_ROOT_PATHS = [
|
||||||
"private/var/mobile/profile.profile/browser.db",
|
"private/var/mobile/profile.profile/browser.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FirefoxFavicon(IOSExtraction):
|
class FirefoxFavicon(IOSExtraction):
|
||||||
"""This module extracts all Firefox favicon"""
|
"""This module extracts all Firefox favicon"""
|
||||||
|
|
||||||
|
@ -39,8 +40,8 @@ class FirefoxFavicon(IOSExtraction):
|
||||||
return
|
return
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if (self.indicators.check_domain(result.get("url", "")) or
|
if (self.indicators.check_domain(result.get("url", "")) or
|
||||||
self.indicators.check_domain(result.get("history_url", ""))):
|
self.indicators.check_domain(result.get("history_url", ""))):
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
|
@ -17,6 +17,7 @@ FIREFOX_HISTORY_ROOT_PATHS = [
|
||||||
"private/var/mobile/profile.profile/browser.db",
|
"private/var/mobile/profile.profile/browser.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FirefoxHistory(IOSExtraction):
|
class FirefoxHistory(IOSExtraction):
|
||||||
"""This module extracts all Firefox visits and tries to detect potential
|
"""This module extracts all Firefox visits and tries to detect potential
|
||||||
network injection attacks.
|
network injection attacks.
|
||||||
|
|
|
@ -18,6 +18,7 @@ IDSTATUSCACHE_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/IdentityServices/idstatuscache.plist",
|
"private/var/mobile/Library/IdentityServices/idstatuscache.plist",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IDStatusCache(IOSExtraction):
|
class IDStatusCache(IOSExtraction):
|
||||||
"""Extracts Apple Authentication information from idstatuscache.plist"""
|
"""Extracts Apple Authentication information from idstatuscache.plist"""
|
||||||
|
|
||||||
|
@ -91,5 +92,5 @@ class IDStatusCache(IOSExtraction):
|
||||||
self.file_path = idstatuscache_path
|
self.file_path = idstatuscache_path
|
||||||
self.log.info("Found IDStatusCache plist at path: %s", self.file_path)
|
self.log.info("Found IDStatusCache plist at path: %s", self.file_path)
|
||||||
self._extract_idstatuscache_entries(self.file_path)
|
self._extract_idstatuscache_entries(self.file_path)
|
||||||
|
|
||||||
self.log.info("Extracted a total of %d ID Status Cache entries", len(self.results))
|
self.log.info("Extracted a total of %d ID Status Cache entries", len(self.results))
|
||||||
|
|
|
@ -16,6 +16,7 @@ INTERACTIONC_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/CoreDuet/People/interactionC.db",
|
"private/var/mobile/Library/CoreDuet/People/interactionC.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class InteractionC(IOSExtraction):
|
class InteractionC(IOSExtraction):
|
||||||
"""This module extracts data from InteractionC db."""
|
"""This module extracts data from InteractionC db."""
|
||||||
|
|
||||||
|
@ -54,8 +55,8 @@ class InteractionC(IOSExtraction):
|
||||||
"timestamp": record[ts],
|
"timestamp": record[ts],
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": ts,
|
"event": ts,
|
||||||
"data": f"[{record['bundle_id']}] {record['account']} - from {record['sender_display_name']} " \
|
"data": f"[{record['bundle_id']}] {record['account']} - from {record['sender_display_name']} "
|
||||||
f"({record['sender_identifier']}) to {record['recipient_display_name']} " \
|
f"({record['sender_identifier']}) to {record['recipient_display_name']} "
|
||||||
f"({record['recipient_identifier']}): {record['content']}"
|
f"({record['recipient_identifier']}): {record['content']}"
|
||||||
})
|
})
|
||||||
processed.append(record[ts])
|
processed.append(record[ts])
|
||||||
|
@ -123,8 +124,7 @@ class InteractionC(IOSExtraction):
|
||||||
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
||||||
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK;
|
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK;
|
||||||
""")
|
""")
|
||||||
|
# names = [description[0] for description in cur.description]
|
||||||
names = [description[0] for description in cur.description]
|
|
||||||
|
|
||||||
for row in cur:
|
for row in cur:
|
||||||
self.results.append({
|
self.results.append({
|
||||||
|
|
|
@ -17,6 +17,7 @@ LOCATIOND_ROOT_PATHS = [
|
||||||
"private/var/root/Library/Caches/locationd/clients.plist"
|
"private/var/root/Library/Caches/locationd/clients.plist"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class LocationdClients(IOSExtraction):
|
class LocationdClients(IOSExtraction):
|
||||||
"""Extract information from apps who used geolocation."""
|
"""Extract information from apps who used geolocation."""
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ DATAUSAGE_ROOT_PATHS = [
|
||||||
"private/var/wireless/Library/Databases/DataUsage.sqlite",
|
"private/var/wireless/Library/Databases/DataUsage.sqlite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Datausage(NetBase):
|
class Datausage(NetBase):
|
||||||
"""This class extracts data from DataUsage.sqlite and attempts to identify
|
"""This class extracts data from DataUsage.sqlite and attempts to identify
|
||||||
any suspicious processes if running on a full filesystem dump.
|
any suspicious processes if running on a full filesystem dump.
|
||||||
|
|
|
@ -16,6 +16,7 @@ OSANALYTICS_ADDAILY_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/Preferences/com.apple.osanalytics.addaily.plist",
|
"private/var/mobile/Library/Preferences/com.apple.osanalytics.addaily.plist",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class OSAnalyticsADDaily(IOSExtraction):
|
class OSAnalyticsADDaily(IOSExtraction):
|
||||||
"""Extract network usage information by process, from com.apple.osanalytics.addaily.plist"""
|
"""Extract network usage information by process, from com.apple.osanalytics.addaily.plist"""
|
||||||
|
|
||||||
|
@ -34,14 +35,14 @@ class OSAnalyticsADDaily(IOSExtraction):
|
||||||
"event": "osanalytics_addaily",
|
"event": "osanalytics_addaily",
|
||||||
"data": record_data,
|
"data": record_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self):
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if self.indicators.check_process(result["package"]):
|
if self.indicators.check_process(result["package"]):
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self._find_ios_database(backup_ids=OSANALYTICS_ADDAILY_BACKUP_IDS,
|
self._find_ios_database(backup_ids=OSANALYTICS_ADDAILY_BACKUP_IDS,
|
||||||
|
|
|
@ -19,6 +19,7 @@ SAFARI_BROWSER_STATE_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/Safari/BrowserState.db",
|
"private/var/mobile/Containers/Data/Application/*/Library/Safari/BrowserState.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SafariBrowserState(IOSExtraction):
|
class SafariBrowserState(IOSExtraction):
|
||||||
"""This module extracts all Safari browser state records."""
|
"""This module extracts all Safari browser state records."""
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ class SafariBrowserState(IOSExtraction):
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not "session_data" in result:
|
if "session_data" not in result:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for session_entry in result["session_data"]:
|
for session_entry in result["session_data"]:
|
||||||
|
|
|
@ -17,6 +17,7 @@ SAFARI_HISTORY_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/Library/Safari/History.db",
|
"private/var/mobile/Containers/Data/Application/*/Library/Safari/History.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SafariHistory(IOSExtraction):
|
class SafariHistory(IOSExtraction):
|
||||||
"""This module extracts all Safari visits and tries to detect potential
|
"""This module extracts all Safari visits and tries to detect potential
|
||||||
network injection attacks.
|
network injection attacks.
|
||||||
|
@ -62,7 +63,7 @@ class SafariHistory(IOSExtraction):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.log.info("Found HTTP redirect to different domain: \"%s\" -> \"%s\"",
|
self.log.info("Found HTTP redirect to different domain: \"%s\" -> \"%s\"",
|
||||||
origin_domain, redirect_domain)
|
origin_domain, redirect_domain)
|
||||||
|
|
||||||
redirect_time = convert_mactime_to_unix(redirect["timestamp"])
|
redirect_time = convert_mactime_to_unix(redirect["timestamp"])
|
||||||
origin_time = convert_mactime_to_unix(result["timestamp"])
|
origin_time = convert_mactime_to_unix(result["timestamp"])
|
||||||
|
|
|
@ -18,6 +18,7 @@ SMS_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/SMS/sms.db",
|
"private/var/mobile/Library/SMS/sms.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SMS(IOSExtraction):
|
class SMS(IOSExtraction):
|
||||||
"""This module extracts all SMS messages containing links."""
|
"""This module extracts all SMS messages containing links."""
|
||||||
|
|
||||||
|
@ -67,8 +68,8 @@ class SMS(IOSExtraction):
|
||||||
# We base64 escape some of the attributes that could contain
|
# We base64 escape some of the attributes that could contain
|
||||||
# binary data.
|
# binary data.
|
||||||
if (names[index] == "attributedBody" or
|
if (names[index] == "attributedBody" or
|
||||||
names[index] == "payload_data" or
|
names[index] == "payload_data" or
|
||||||
names[index] == "message_summary_info") and value:
|
names[index] == "message_summary_info") and value:
|
||||||
value = b64encode(value).decode()
|
value = b64encode(value).decode()
|
||||||
|
|
||||||
# We store the value of each column under the proper key.
|
# We store the value of each column under the proper key.
|
||||||
|
|
|
@ -17,6 +17,7 @@ SMS_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/SMS/sms.db",
|
"private/var/mobile/Library/SMS/sms.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SMSAttachments(IOSExtraction):
|
class SMSAttachments(IOSExtraction):
|
||||||
"""This module extracts all info about SMS/iMessage attachments."""
|
"""This module extracts all info about SMS/iMessage attachments."""
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ class SMSAttachments(IOSExtraction):
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
attachment.ROWID as "attachment_id",
|
attachment.ROWID as "attachment_id",
|
||||||
attachment.*,
|
attachment.*,
|
||||||
message.service as "service",
|
message.service as "service",
|
||||||
handle.id as "phone_number"
|
handle.id as "phone_number"
|
||||||
FROM attachment
|
FROM attachment
|
||||||
|
@ -73,7 +74,7 @@ class SMSAttachments(IOSExtraction):
|
||||||
attachment["filename"] = attachment["filename"] or "NULL"
|
attachment["filename"] = attachment["filename"] or "NULL"
|
||||||
|
|
||||||
if (attachment["filename"].startswith("/var/tmp/") and attachment["filename"].endswith("-1") and
|
if (attachment["filename"].startswith("/var/tmp/") and attachment["filename"].endswith("-1") and
|
||||||
attachment["direction"] == "received"):
|
attachment["direction"] == "received"):
|
||||||
self.log.warn(f"Suspicious iMessage attachment '{attachment['filename']}' on {attachment['isodate']}")
|
self.log.warn(f"Suspicious iMessage attachment '{attachment['filename']}' on {attachment['isodate']}")
|
||||||
self.detected.append(attachment)
|
self.detected.append(attachment)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ AUTH_REASONS = {
|
||||||
12: "app_type_policy",
|
12: "app_type_policy",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TCC(IOSExtraction):
|
class TCC(IOSExtraction):
|
||||||
"""This module extracts records from the TCC.db SQLite database."""
|
"""This module extracts records from the TCC.db SQLite database."""
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ class TCC(IOSExtraction):
|
||||||
def process_db(self, file_path):
|
def process_db(self, file_path):
|
||||||
conn = sqlite3.connect(file_path)
|
conn = sqlite3.connect(file_path)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute("""SELECT
|
cur.execute("""SELECT
|
||||||
service, client, client_type, auth_value, auth_reason, last_modified
|
service, client, client_type, auth_value, auth_reason, last_modified
|
||||||
FROM access;""")
|
FROM access;""")
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/observations.db",
|
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/observations.db",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WebkitResourceLoadStatistics(IOSExtraction):
|
class WebkitResourceLoadStatistics(IOSExtraction):
|
||||||
"""This module extracts records from WebKit ResourceLoadStatistics observations.db."""
|
"""This module extracts records from WebKit ResourceLoadStatistics observations.db."""
|
||||||
# TODO: Add serialize().
|
# TODO: Add serialize().
|
||||||
|
@ -38,7 +39,7 @@ class WebkitResourceLoadStatistics(IOSExtraction):
|
||||||
for item in items:
|
for item in items:
|
||||||
if self.indicators.check_domain(item["registrable_domain"]):
|
if self.indicators.check_domain(item["registrable_domain"]):
|
||||||
if key not in self.detected:
|
if key not in self.detected:
|
||||||
self.detected[key] = [item,]
|
self.detected[key] = [item, ]
|
||||||
else:
|
else:
|
||||||
self.detected[key].append(item)
|
self.detected[key].append(item)
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ class WebkitResourceLoadStatistics(IOSExtraction):
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not key in self.results:
|
if key not in self.results:
|
||||||
self.results[key] = []
|
self.results[key] = []
|
||||||
|
|
||||||
for row in cur:
|
for row in cur:
|
||||||
|
|
|
@ -20,6 +20,7 @@ WEBKIT_SESSION_RESOURCE_LOG_ROOT_PATHS = [
|
||||||
"private/var/mobile/Library/WebClips/*/Storage/full_browsing_session_resourceLog.plist",
|
"private/var/mobile/Library/WebClips/*/Storage/full_browsing_session_resourceLog.plist",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WebkitSessionResourceLog(IOSExtraction):
|
class WebkitSessionResourceLog(IOSExtraction):
|
||||||
"""This module extracts records from WebKit browsing session
|
"""This module extracts records from WebKit browsing session
|
||||||
resource logs, and checks them against any provided list of
|
resource logs, and checks them against any provided list of
|
||||||
|
|
|
@ -20,6 +20,7 @@ WHATSAPP_ROOT_PATHS = [
|
||||||
"private/var/mobile/Containers/Shared/AppGroup/*/ChatStorage.sqlite",
|
"private/var/mobile/Containers/Shared/AppGroup/*/ChatStorage.sqlite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Whatsapp(IOSExtraction):
|
class Whatsapp(IOSExtraction):
|
||||||
"""This module extracts all WhatsApp messages containing links."""
|
"""This module extracts all WhatsApp messages containing links."""
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ class NetBase(IOSExtraction):
|
||||||
def serialize(self, record):
|
def serialize(self, record):
|
||||||
record_data = f"{record['proc_name']} (Bundle ID: {record['bundle_id']}, ID: {record['proc_id']})"
|
record_data = f"{record['proc_name']} (Bundle ID: {record['bundle_id']}, ID: {record['proc_id']})"
|
||||||
record_data_usage = record_data + f" WIFI IN: {record['wifi_in']}, WIFI OUT: {record['wifi_out']} - " \
|
record_data_usage = record_data + f" WIFI IN: {record['wifi_in']}, WIFI OUT: {record['wifi_out']} - " \
|
||||||
f"WWAN IN: {record['wwan_in']}, WWAN OUT: {record['wwan_out']}"
|
f"WWAN IN: {record['wwan_in']}, WWAN OUT: {record['wwan_out']}"
|
||||||
|
|
||||||
records = [{
|
records = [{
|
||||||
"timestamp": record["live_isodate"],
|
"timestamp": record["live_isodate"],
|
||||||
|
|
|
@ -233,11 +233,13 @@ IPHONE_IOS_VERSIONS = [
|
||||||
{"build": "19B74", "version": "15.1"},
|
{"build": "19B74", "version": "15.1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_device_desc_from_id(identifier, devices_list=IPHONE_MODELS):
|
def get_device_desc_from_id(identifier, devices_list=IPHONE_MODELS):
|
||||||
for model in IPHONE_MODELS:
|
for model in IPHONE_MODELS:
|
||||||
if identifier == model["identifier"]:
|
if identifier == model["identifier"]:
|
||||||
return model["description"]
|
return model["description"]
|
||||||
|
|
||||||
|
|
||||||
def find_version_by_build(build):
|
def find_version_by_build(build):
|
||||||
build = build.upper()
|
build = build.upper()
|
||||||
for version in IPHONE_IOS_VERSIONS:
|
for version in IPHONE_IOS_VERSIONS:
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -30,6 +30,7 @@ requires = (
|
||||||
"libusb1>=2.0.1",
|
"libusb1>=2.0.1",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_package_data(package):
|
def get_package_data(package):
|
||||||
walk = [(dirpath.replace(package + os.sep, "", 1), filenames)
|
walk = [(dirpath.replace(package + os.sep, "", 1), filenames)
|
||||||
for dirpath, dirnames, filenames in os.walk(package)
|
for dirpath, dirnames, filenames in os.walk(package)
|
||||||
|
@ -41,6 +42,7 @@ def get_package_data(package):
|
||||||
for filename in filenames])
|
for filename in filenames])
|
||||||
return {package: filepaths}
|
return {package: filepaths}
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="mvt",
|
name="mvt",
|
||||||
version=MVT_VERSION,
|
version=MVT_VERSION,
|
||||||
|
|
Loading…
Reference in New Issue