mirror of
https://github.com/mvt-project/mvt
synced 2025-10-21 22:42:15 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1211991aa | ||
|
|
8ae9ca328c | ||
|
|
0e2eb51732 | ||
|
|
b35cd4bc73 | ||
|
|
1b4f99a31d |
@@ -139,7 +139,7 @@ def check_adb(ctx, iocs, output, fast, list_modules, module, serial):
|
|||||||
|
|
||||||
m = adb_module(output_folder=output, fast_mode=fast,
|
m = adb_module(output_folder=output, fast_mode=fast,
|
||||||
log=logging.getLogger(adb_module.__module__))
|
log=logging.getLogger(adb_module.__module__))
|
||||||
if indicators.ioc_count:
|
if indicators.total_ioc_count:
|
||||||
m.indicators = indicators
|
m.indicators = indicators
|
||||||
m.indicators.log = m.log
|
m.indicators.log = m.log
|
||||||
if serial:
|
if serial:
|
||||||
@@ -190,7 +190,7 @@ def check_backup(ctx, iocs, output, backup_path, serial):
|
|||||||
for module in BACKUP_MODULES:
|
for module in BACKUP_MODULES:
|
||||||
m = module(base_folder=backup_path, output_folder=output,
|
m = module(base_folder=backup_path, output_folder=output,
|
||||||
log=logging.getLogger(module.__module__))
|
log=logging.getLogger(module.__module__))
|
||||||
if indicators.ioc_count:
|
if indicators.total_ioc_count:
|
||||||
m.indicators = indicators
|
m.indicators = indicators
|
||||||
m.indicators.log = m.log
|
m.indicators.log = m.log
|
||||||
if serial:
|
if serial:
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ class Packages(AndroidExtraction):
|
|||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
|
|
||||||
packages = self._adb_command("pm list packages -U -u -i -f")
|
packages = self._adb_command("pm list packages -U -u -i -f")
|
||||||
|
if packages.strip() == "Error: Unknown option: -U":
|
||||||
|
packages = self._adb_command("pm list packages -u -i -f")
|
||||||
|
|
||||||
for line in packages.split("\n"):
|
for line in packages.split("\n"):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line.startswith("package:"):
|
if not line.startswith("package:"):
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
|||||||
import requests
|
import requests
|
||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
MVT_VERSION = "1.4.5"
|
MVT_VERSION = "1.4.6"
|
||||||
|
|
||||||
|
|
||||||
def check_for_updates():
|
def check_for_updates():
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ def check_backup(ctx, iocs, output, fast, backup_path, list_modules, module):
|
|||||||
m = backup_module(base_folder=backup_path, output_folder=output, fast_mode=fast,
|
m = backup_module(base_folder=backup_path, output_folder=output, fast_mode=fast,
|
||||||
log=logging.getLogger(backup_module.__module__))
|
log=logging.getLogger(backup_module.__module__))
|
||||||
m.is_backup = True
|
m.is_backup = True
|
||||||
if indicators.ioc_count:
|
if indicators.total_ioc_count > 0:
|
||||||
m.indicators = indicators
|
m.indicators = indicators
|
||||||
m.indicators.log = m.log
|
m.indicators.log = m.log
|
||||||
|
|
||||||
@@ -182,6 +182,10 @@ def check_backup(ctx, iocs, output, fast, backup_path, list_modules, module):
|
|||||||
if len(timeline_detected) > 0:
|
if len(timeline_detected) > 0:
|
||||||
save_timeline(timeline_detected, os.path.join(output, "timeline_detected.csv"))
|
save_timeline(timeline_detected, os.path.join(output, "timeline_detected.csv"))
|
||||||
|
|
||||||
|
if len(timeline_detected) > 0:
|
||||||
|
log.warning("The analysis of the backup produced %d detections!",
|
||||||
|
len(timeline_detected))
|
||||||
|
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Command: check-fs
|
# Command: check-fs
|
||||||
@@ -225,7 +229,7 @@ def check_fs(ctx, iocs, output, fast, dump_path, list_modules, module):
|
|||||||
log=logging.getLogger(fs_module.__module__))
|
log=logging.getLogger(fs_module.__module__))
|
||||||
|
|
||||||
m.is_fs_dump = True
|
m.is_fs_dump = True
|
||||||
if indicators.ioc_count:
|
if indicators.total_ioc_count > 0:
|
||||||
m.indicators = indicators
|
m.indicators = indicators
|
||||||
m.indicators.log = m.log
|
m.indicators.log = m.log
|
||||||
|
|
||||||
@@ -239,20 +243,23 @@ def check_fs(ctx, iocs, output, fast, dump_path, list_modules, module):
|
|||||||
if len(timeline_detected) > 0:
|
if len(timeline_detected) > 0:
|
||||||
save_timeline(timeline_detected, os.path.join(output, "timeline_detected.csv"))
|
save_timeline(timeline_detected, os.path.join(output, "timeline_detected.csv"))
|
||||||
|
|
||||||
|
if len(timeline_detected) > 0:
|
||||||
|
log.warning("The analysis of the filesystem produced %d detections!",
|
||||||
|
len(timeline_detected))
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Command: check-iocs
|
# Command: check-iocs
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
@cli.command("check-iocs", help="Compare stored JSON results to provided indicators")
|
@cli.command("check-iocs", help="Compare stored JSON results to provided indicators")
|
||||||
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
|
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
|
||||||
default=[], required=True, help=HELP_MSG_IOC)
|
default=[], help=HELP_MSG_IOC)
|
||||||
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
|
||||||
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
@click.option("--module", "-m", help=HELP_MSG_MODULE)
|
||||||
@click.argument("FOLDER", type=click.Path(exists=True))
|
@click.argument("FOLDER", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_iocs(ctx, iocs, list_modules, module, folder):
|
def check_iocs(ctx, iocs, list_modules, module, folder):
|
||||||
all_modules = []
|
all_modules = []
|
||||||
for entry in BACKUP_MODULES + FS_MODULES:
|
for entry in BACKUP_MODULES + FS_MODULES + MIXED_MODULES:
|
||||||
if entry not in all_modules:
|
if entry not in all_modules:
|
||||||
all_modules.append(entry)
|
all_modules.append(entry)
|
||||||
|
|
||||||
@@ -268,6 +275,7 @@ def check_iocs(ctx, iocs, list_modules, module, folder):
|
|||||||
indicators = Indicators(log=log)
|
indicators = Indicators(log=log)
|
||||||
indicators.load_indicators_files(iocs)
|
indicators.load_indicators_files(iocs)
|
||||||
|
|
||||||
|
total_detections = 0
|
||||||
for file_name in os.listdir(folder):
|
for file_name in os.listdir(folder):
|
||||||
name_only, ext = os.path.splitext(file_name)
|
name_only, ext = os.path.splitext(file_name)
|
||||||
file_path = os.path.join(folder, file_name)
|
file_path = os.path.join(folder, file_name)
|
||||||
@@ -284,7 +292,7 @@ def check_iocs(ctx, iocs, list_modules, module, folder):
|
|||||||
|
|
||||||
m = iocs_module.from_json(file_path,
|
m = iocs_module.from_json(file_path,
|
||||||
log=logging.getLogger(iocs_module.__module__))
|
log=logging.getLogger(iocs_module.__module__))
|
||||||
if indicators.ioc_count:
|
if indicators.total_ioc_count > 0:
|
||||||
m.indicators = indicators
|
m.indicators = indicators
|
||||||
m.indicators.log = m.log
|
m.indicators.log = m.log
|
||||||
|
|
||||||
@@ -292,6 +300,12 @@ def check_iocs(ctx, iocs, list_modules, module, folder):
|
|||||||
m.check_indicators()
|
m.check_indicators()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
total_detections += len(m.detected)
|
||||||
|
|
||||||
|
if total_detections > 0:
|
||||||
|
log.warning("The check of the results produced %d detections!",
|
||||||
|
total_detections)
|
||||||
|
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ class Manifest(IOSExtraction):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if "relative_path" not in result:
|
if not result.get("relative_path"):
|
||||||
continue
|
|
||||||
if not result["relative_path"]:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if result["domain"]:
|
if result["domain"]:
|
||||||
@@ -84,15 +82,14 @@ class Manifest(IOSExtraction):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if self.indicators.check_file_path("/" + result["relative_path"]):
|
if self.indicators.check_file_path("/" + result["relative_path"]):
|
||||||
self.log.warning("Found a known malicious file at path: %s", result["relative_path"])
|
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
relPath = result["relative_path"].lower()
|
rel_path = result["relative_path"].lower()
|
||||||
for ioc in self.indicators.ioc_domains:
|
for ioc in self.indicators.get_iocs("domains"):
|
||||||
if ioc.lower() in relPath:
|
if ioc["value"].lower() in rel_path:
|
||||||
self.log.warning("Found mention of domain \"%s\" in a backup file with path: %s",
|
self.log.warning("Found mention of domain \"%s\" in a backup file with path: %s",
|
||||||
ioc, relPath)
|
ioc["value"], rel_path)
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|||||||
@@ -37,20 +37,20 @@ class Analytics(IOSExtraction):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
for ioc in self.indicators.ioc_processes:
|
for value in result.values():
|
||||||
for key in result.keys():
|
if not isinstance(value, str):
|
||||||
if ioc == result[key]:
|
continue
|
||||||
self.log.warning("Found mention of a malicious process \"%s\" in %s file at %s",
|
|
||||||
ioc, result["artifact"], result["timestamp"])
|
if self.indicators.check_process(value):
|
||||||
self.detected.append(result)
|
self.log.warning("Found mention of a malicious process \"%s\" in %s file at %s",
|
||||||
break
|
value, result["artifact"], result["timestamp"])
|
||||||
for ioc in self.indicators.ioc_domains:
|
self.detected.append(result)
|
||||||
for key in result.keys():
|
continue
|
||||||
if ioc in str(result[key]):
|
|
||||||
self.log.warning("Found mention of a malicious domain \"%s\" in %s file at %s",
|
if self.indicators.check_domain(value):
|
||||||
ioc, result["artifact"], result["timestamp"])
|
self.log.warning("Found mention of a malicious domain \"%s\" in %s file at %s",
|
||||||
self.detected.append(result)
|
value, result["artifact"], result["timestamp"])
|
||||||
break
|
self.detected.append(result)
|
||||||
|
|
||||||
def _extract_analytics_data(self):
|
def _extract_analytics_data(self):
|
||||||
artifact = self.file_path.split("/")[-1]
|
artifact = self.file_path.split("/")[-1]
|
||||||
@@ -101,6 +101,7 @@ class Analytics(IOSExtraction):
|
|||||||
timestamp = ""
|
timestamp = ""
|
||||||
data = plistlib.loads(row[1])
|
data = plistlib.loads(row[1])
|
||||||
data["timestamp"] = timestamp
|
data["timestamp"] = timestamp
|
||||||
|
|
||||||
data["artifact"] = artifact
|
data["artifact"] = artifact
|
||||||
|
|
||||||
self.results.append(data)
|
self.results.append(data)
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ class CacheFiles(IOSExtraction):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.detected = {}
|
self.detected = {}
|
||||||
for key, items in self.results.items():
|
for key, values in self.results.items():
|
||||||
for item in items:
|
for value in values:
|
||||||
if self.indicators.check_domain(item["url"]):
|
if self.indicators.check_domain(value["url"]):
|
||||||
if key not in self.detected:
|
if key not in self.detected:
|
||||||
self.detected[key] = [item, ]
|
self.detected[key] = [value, ]
|
||||||
else:
|
else:
|
||||||
self.detected[key].append(item)
|
self.detected[key].append(value)
|
||||||
|
|
||||||
def _process_cache_file(self, file_path):
|
def _process_cache_file(self, file_path):
|
||||||
self.log.info("Processing cache file at path: %s", file_path)
|
self.log.info("Processing cache file at path: %s", file_path)
|
||||||
|
|||||||
@@ -37,19 +37,22 @@ class Filesystem(IOSExtraction):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
|
if "path" not in result:
|
||||||
|
continue
|
||||||
|
|
||||||
if self.indicators.check_file_path(result["path"]):
|
if self.indicators.check_file_path(result["path"]):
|
||||||
self.log.warning("Found a known malicious file path at path: %s", result["path"])
|
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|
||||||
# If we are instructed to run fast, we skip this.
|
# If we are instructed to run fast, we skip the rest.
|
||||||
if self.fast_mode:
|
if self.fast_mode:
|
||||||
self.log.info("Flag --fast was enabled: skipping extended search for suspicious files/processes")
|
continue
|
||||||
else:
|
|
||||||
for ioc in self.indicators.ioc_processes:
|
for ioc in self.indicators.get_iocs("processes"):
|
||||||
parts = result["path"].split("/")
|
parts = result["path"].split("/")
|
||||||
if ioc in parts:
|
if ioc["value"] in parts:
|
||||||
self.log.warning("Found a known malicious file/process at path: %s", result["path"])
|
self.log.warning("Found known suspicious process name mentioned in file at path \"%s\" matching indicators from \"%s\"",
|
||||||
self.detected.append(result)
|
result["path"], ioc["name"])
|
||||||
|
self.detected.append(result)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
for root, dirs, files in os.walk(self.base_folder):
|
for root, dirs, files in os.walk(self.base_folder):
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ class ShutdownLog(IOSExtraction):
|
|||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if self.indicators.check_file_path(result["client"]):
|
if self.indicators.check_file_path(result["client"]):
|
||||||
self.log.warning("Found mention of a known malicious file \"%s\" in shutdown.log",
|
|
||||||
result["client"])
|
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for ioc in self.indicators.ioc_processes:
|
for ioc in self.indicators.get_iocs("processes"):
|
||||||
parts = result["client"].split("/")
|
parts = result["client"].split("/")
|
||||||
if ioc in parts:
|
if ioc in parts:
|
||||||
self.log.warning("Found mention of a known malicious process \"%s\" in shutdown.log",
|
self.log.warning("Found mention of a known malicious process \"%s\" in shutdown.log",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class Whatsapp(IOSExtraction):
|
|||||||
links_text = ""
|
links_text = ""
|
||||||
if record["links"]:
|
if record["links"]:
|
||||||
links_text = " - Embedded links: " + ", ".join(record["links"])
|
links_text = " - Embedded links: " + ", ".join(record["links"])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"timestamp": record.get("isodate"),
|
"timestamp": record.get("isodate"),
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
@@ -47,7 +48,7 @@ class Whatsapp(IOSExtraction):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for message in self.results:
|
for message in self.results:
|
||||||
if self.indicators.check_domains(message["links"]):
|
if self.indicators.check_domains(message.get("links", [])):
|
||||||
self.detected.append(message)
|
self.detected.append(message)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -83,14 +84,15 @@ class Whatsapp(IOSExtraction):
|
|||||||
message["isodate"] = convert_timestamp_to_iso(convert_mactime_to_unix(message.get("ZMESSAGEDATE")))
|
message["isodate"] = convert_timestamp_to_iso(convert_mactime_to_unix(message.get("ZMESSAGEDATE")))
|
||||||
message["ZTEXT"] = message["ZTEXT"] if message["ZTEXT"] else ""
|
message["ZTEXT"] = message["ZTEXT"] if message["ZTEXT"] else ""
|
||||||
|
|
||||||
# Extract links from the WhatsApp message. URLs can be stored in multiple fields/columns. Check each of them!
|
# Extract links from the WhatsApp message. URLs can be stored in multiple fields/columns.
|
||||||
|
# Check each of them!
|
||||||
message_links = []
|
message_links = []
|
||||||
fields_with_links = ["ZTEXT", "ZMATCHEDTEXT", "ZMEDIAURL", "ZCONTENT1", "ZCONTENT2"]
|
fields_with_links = ["ZTEXT", "ZMATCHEDTEXT", "ZMEDIAURL", "ZCONTENT1", "ZCONTENT2"]
|
||||||
for field in fields_with_links:
|
for field in fields_with_links:
|
||||||
if message.get(field):
|
if message.get(field):
|
||||||
message_links.extend(check_for_links(message.get(field, "")))
|
message_links.extend(check_for_links(message.get(field, "")))
|
||||||
|
|
||||||
# Remove WhatsApp internal media URLs
|
# Remove WhatsApp internal media URLs.
|
||||||
filtered_links = []
|
filtered_links = []
|
||||||
for link in message_links:
|
for link in message_links:
|
||||||
if not (link.startswith("https://mmg-fna.whatsapp.net/") or link.startswith("https://mmg.whatsapp.net/")):
|
if not (link.startswith("https://mmg-fna.whatsapp.net/") or link.startswith("https://mmg.whatsapp.net/")):
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class TestIndicators:
|
|||||||
def test_parse_stix2(self, indicator_file):
|
def test_parse_stix2(self, indicator_file):
|
||||||
ind = Indicators(log=logging)
|
ind = Indicators(log=logging)
|
||||||
ind.load_indicators_files([indicator_file], load_default=False)
|
ind.load_indicators_files([indicator_file], load_default=False)
|
||||||
assert ind.ioc_count == 4
|
assert ind.ioc_files[0]["count"] == 4
|
||||||
assert len(ind.ioc_domains) == 1
|
assert len(ind.ioc_files[0]["domains"]) == 1
|
||||||
assert len(ind.ioc_emails) == 1
|
assert len(ind.ioc_files[0]["emails"]) == 1
|
||||||
assert len(ind.ioc_files) == 1
|
assert len(ind.ioc_files[0]["file_names"]) == 1
|
||||||
assert len(ind.ioc_processes) == 1
|
assert len(ind.ioc_files[0]["processes"]) == 1
|
||||||
|
|
||||||
def test_check_domain(self, indicator_file):
|
def test_check_domain(self, indicator_file):
|
||||||
ind = Indicators(log=logging)
|
ind = Indicators(log=logging)
|
||||||
@@ -28,5 +28,5 @@ class TestIndicators:
|
|||||||
def test_env_stix(self, indicator_file):
|
def test_env_stix(self, indicator_file):
|
||||||
os.environ["MVT_STIX2"] = indicator_file
|
os.environ["MVT_STIX2"] = indicator_file
|
||||||
ind = Indicators(log=logging)
|
ind = Indicators(log=logging)
|
||||||
ind.load_indicators_files([indicator_file], load_default=False)
|
ind.load_indicators_files([], load_default=False)
|
||||||
assert ind.ioc_count == 4
|
assert ind.total_ioc_count == 4
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class TestDatausageModule:
|
|||||||
ind = Indicators(log=logging)
|
ind = Indicators(log=logging)
|
||||||
ind.parse_stix2(indicator_file)
|
ind.parse_stix2(indicator_file)
|
||||||
# Adds a file that exists in the manifest.
|
# Adds a file that exists in the manifest.
|
||||||
ind.ioc_processes[0] = "CumulativeUsageTracker"
|
ind.ioc_files[0]["processes"].append("CumulativeUsageTracker")
|
||||||
m.indicators = ind
|
m.indicators = ind
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.detected) == 2
|
assert len(m.detected) == 2
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ class TestManifestModule:
|
|||||||
m = Manifest(base_folder=get_backup_folder(), log=logging, results=[])
|
m = Manifest(base_folder=get_backup_folder(), log=logging, results=[])
|
||||||
ind = Indicators(log=logging)
|
ind = Indicators(log=logging)
|
||||||
ind.parse_stix2(indicator_file)
|
ind.parse_stix2(indicator_file)
|
||||||
# Adds a file that exists in the manifest
|
ind.ioc_files[0]["file_names"].append("com.apple.CoreBrightness.plist")
|
||||||
ind.ioc_files[0] = "com.apple.CoreBrightness.plist"
|
|
||||||
m.indicators = ind
|
m.indicators = ind
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.detected) == 1
|
assert len(m.detected) == 1
|
||||||
|
|||||||
Reference in New Issue
Block a user