mirror of
https://github.com/mvt-project/mvt.git
synced 2026-03-07 19:40:47 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79a01c45cc | ||
|
|
a440d12377 | ||
|
|
8085888c0c | ||
|
|
c2617fe778 | ||
|
|
2e1243864c | ||
|
|
ba5ff9b38c | ||
|
|
3fccebe132 | ||
|
|
1265b366c1 | ||
|
|
c944fb3234 | ||
|
|
e6b4d17027 | ||
|
|
f55ac36189 | ||
|
|
550d6037a6 | ||
|
|
e875c978c9 |
@@ -78,8 +78,8 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
try:
|
try:
|
||||||
self._adb_download(remote_path, local_path)
|
self._adb_download(remote_path, local_path)
|
||||||
except InsufficientPrivileges:
|
except InsufficientPrivileges:
|
||||||
log.warn("Unable to pull package file from %s: insufficient privileges, it might be a system app",
|
log.error("Unable to pull package file from %s: insufficient privileges, it might be a system app",
|
||||||
remote_path)
|
remote_path)
|
||||||
self._adb_reconnect()
|
self._adb_reconnect()
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ ANDROID_DANGEROUS_SETTINGS = [
|
|||||||
"key": "send_action_app_error",
|
"key": "send_action_app_error",
|
||||||
"safe_value": "1",
|
"safe_value": "1",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "enabled installation of non Google Play apps",
|
||||||
|
"key": "install_non_market_apps",
|
||||||
|
"safe_value": "0",
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class SMS(AndroidExtraction):
|
|||||||
"timestamp": record["isodate"],
|
"timestamp": record["isodate"],
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": f"sms_{record['direction']}",
|
"event": f"sms_{record['direction']}",
|
||||||
"data": f"{record['address']}: \"{body}\""
|
"data": f"{record.get('address', 'unknown source')}: \"{body}\""
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
def check_indicators(self) -> None:
|
||||||
@@ -70,7 +70,7 @@ class SMS(AndroidExtraction):
|
|||||||
if "body" not in message:
|
if "body" not in message:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# FIXME: check links exported from the body previously
|
# TODO: check links exported from the body previously.
|
||||||
message_links = check_for_links(message["body"])
|
message_links = check_for_links(message["body"])
|
||||||
if self.indicators.check_domains(message_links):
|
if self.indicators.check_domains(message_links):
|
||||||
self.detected.append(message)
|
self.detected.append(message)
|
||||||
@@ -110,10 +110,12 @@ class SMS(AndroidExtraction):
|
|||||||
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
|
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
|
||||||
|
|
||||||
def _extract_sms_adb(self) -> None:
|
def _extract_sms_adb(self) -> None:
|
||||||
"""Use the Android backup command to extract SMS data from the native SMS app
|
"""Use the Android backup command to extract SMS data from the native SMS
|
||||||
|
app.
|
||||||
|
|
||||||
It is crucial to use the under-documented "-nocompress" flag to disable the non-standard Java compression
|
It is crucial to use the under-documented "-nocompress" flag to disable
|
||||||
algorithim. This module only supports an unencrypted ADB backup.
|
the non-standard Java compression algorithm. This module only supports
|
||||||
|
an unencrypted ADB backup.
|
||||||
"""
|
"""
|
||||||
backup_tar = self._generate_backup("com.android.providers.telephony")
|
backup_tar = self._generate_backup("com.android.providers.telephony")
|
||||||
if not backup_tar:
|
if not backup_tar:
|
||||||
@@ -122,7 +124,8 @@ class SMS(AndroidExtraction):
|
|||||||
try:
|
try:
|
||||||
self.results = parse_tar_for_sms(backup_tar)
|
self.results = parse_tar_for_sms(backup_tar)
|
||||||
except AndroidBackupParsingError:
|
except AndroidBackupParsingError:
|
||||||
self.log.info("Impossible to read SMS from the Android Backup, please extract the SMS and try extracting it with Android Backup Extractor")
|
self.log.info("Impossible to read SMS from the Android Backup, please extract "\
|
||||||
|
"the SMS and try extracting it with Android Backup Extractor")
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
|
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
|
||||||
@@ -139,5 +142,6 @@ class SMS(AndroidExtraction):
|
|||||||
except InsufficientPrivileges:
|
except InsufficientPrivileges:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.log.warn("No SMS database found. Trying extraction of SMS data using Android backup feature.")
|
self.log.warn("No SMS database found. Trying extraction of SMS data using " \
|
||||||
|
"Android backup feature.")
|
||||||
self._extract_sms_adb()
|
self._extract_sms_adb()
|
||||||
|
|||||||
@@ -195,7 +195,8 @@ def save_timeline(timeline: list, timeline_path: str) -> None:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
with open(timeline_path, "a+", encoding="utf-8") as handle:
|
with open(timeline_path, "a+", encoding="utf-8") as handle:
|
||||||
csvoutput = csv.writer(handle, delimiter=",", quotechar="\"")
|
csvoutput = csv.writer(handle, delimiter=",", quotechar="\"",
|
||||||
|
quoting=csv.QUOTE_ALL)
|
||||||
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
|
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
|
||||||
for event in sorted(timeline, key=lambda x: x["timestamp"] if x["timestamp"] is not None else ""):
|
for event in sorted(timeline, key=lambda x: x["timestamp"] if x["timestamp"] is not None else ""):
|
||||||
csvoutput.writerow([
|
csvoutput.writerow([
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from typing import Optional
|
||||||
from tld import get_tld
|
from tld import get_tld
|
||||||
|
|
||||||
SHORTENER_DOMAINS = [
|
SHORTENER_DOMAINS = [
|
||||||
@@ -308,7 +309,7 @@ class URL:
|
|||||||
|
|
||||||
return self.is_shortened
|
return self.is_shortened
|
||||||
|
|
||||||
def unshorten(self) -> None:
|
def unshorten(self) -> Optional[str]:
|
||||||
"""Unshorten the URL by requesting an HTTP HEAD response."""
|
"""Unshorten the URL by requesting an HTTP HEAD response."""
|
||||||
res = requests.head(self.url)
|
res = requests.head(self.url)
|
||||||
if str(res.status_code).startswith("30"):
|
if str(res.status_code).startswith("30"):
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
MVT_VERSION = "2.0"
|
MVT_VERSION = "2.1"
|
||||||
|
|||||||
@@ -31,32 +31,70 @@ class ProfileEvents(IOSExtraction):
|
|||||||
"timestamp": record.get("timestamp"),
|
"timestamp": record.get("timestamp"),
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": "profile_operation",
|
"event": "profile_operation",
|
||||||
"data": f"Process {record.get('process')} started operation {record.get('operation')} of profile {record.get('profile_id')}"
|
"data": f"Process {record.get('process')} started operation " \
|
||||||
|
f"{record.get('operation')} of profile {record.get('profile_id')}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def check_indicators(self) -> None:
|
||||||
|
if not self.indicators:
|
||||||
|
return
|
||||||
|
|
||||||
|
for result in self.results:
|
||||||
|
ioc = self.indicators.check_process(result.get("process"))
|
||||||
|
if ioc:
|
||||||
|
result["matched_indicator"] = ioc
|
||||||
|
self.detected.append(result)
|
||||||
|
continue
|
||||||
|
|
||||||
|
ioc = self.indicators.check_profile(result.get("profile_id"))
|
||||||
|
if ioc:
|
||||||
|
result["matched_indicator"] = ioc
|
||||||
|
self.detected.append(result)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_profile_events(events_file_path) -> list:
|
||||||
|
results = []
|
||||||
|
|
||||||
|
with open(events_file_path, "rb") as handle:
|
||||||
|
events_plist = plistlib.load(handle)
|
||||||
|
|
||||||
|
if "ProfileEvents" not in events_plist:
|
||||||
|
return results
|
||||||
|
|
||||||
|
for event in events_plist["ProfileEvents"]:
|
||||||
|
key = list(event.keys())[0]
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"profile_id": key,
|
||||||
|
"timestamp": "",
|
||||||
|
"operation": "",
|
||||||
|
"process": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in event[key].items():
|
||||||
|
key = key.lower()
|
||||||
|
if key == "timestamp":
|
||||||
|
result["timestamp"] = str(convert_timestamp_to_iso(value))
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
for events_file in self._get_backup_files_from_manifest(relative_path=CONF_PROFILES_EVENTS_RELPATH):
|
for events_file in self._get_backup_files_from_manifest(relative_path=CONF_PROFILES_EVENTS_RELPATH):
|
||||||
events_file_path = self._get_backup_file_from_id(events_file["file_id"])
|
events_file_path = self._get_backup_file_from_id(events_file["file_id"])
|
||||||
if not events_file_path:
|
if not events_file_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with open(events_file_path, "rb") as handle:
|
self.log.info("Found MCProfileEvents.plist file at %s", events_file_path)
|
||||||
events_plist = plistlib.load(handle)
|
|
||||||
|
|
||||||
if "ProfileEvents" not in events_plist:
|
self.results.extend(self.parse_profile_events(events_file_path))
|
||||||
continue
|
|
||||||
|
|
||||||
for event in events_plist["ProfileEvents"]:
|
for result in self.results:
|
||||||
key = list(event.keys())[0]
|
self.log.info("On %s process \"%s\" started operation \"%s\" of profile \"%s\"",
|
||||||
self.log.info("On %s process \"%s\" started operation \"%s\" of profile \"%s\"",
|
result.get("timestamp"), result.get("process"),
|
||||||
event[key].get("timestamp"), event[key].get("process"),
|
result.get("operation"), result.get("profile_id"))
|
||||||
event[key].get("operation"), key)
|
|
||||||
|
|
||||||
self.results.append({
|
|
||||||
"profile_id": key,
|
|
||||||
"timestamp": convert_timestamp_to_iso(event[key].get("timestamp")),
|
|
||||||
"operation": event[key].get("operation"),
|
|
||||||
"process": event[key].get("process"),
|
|
||||||
})
|
|
||||||
|
|
||||||
self.log.info("Extracted %d profile events", len(self.results))
|
self.log.info("Extracted %d profile events", len(self.results))
|
||||||
|
|||||||
Reference in New Issue
Block a user