mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-23 22:03:24 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4e1716729 | ||
|
|
083bc12351 | ||
|
|
cf6d392460 | ||
|
|
95205d8e17 | ||
|
|
38bb583a9e |
@@ -41,6 +41,6 @@ export MVT_STIX2="/home/user/IOC1.stix2:/home/user/IOC2.stix2"
|
||||
- [Predator from Cytrox](https://citizenlab.ca/2021/12/pegasus-vs-predator-dissidents-doubly-infected-iphone-reveals-cytrox-mercenary-spyware/) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-12-16_cytrox/cytrox.stix2))
|
||||
- [This repository](https://github.com/Te-k/stalkerware-indicators) contains IOCs for Android stalkerware including [a STIX MVT-compatible file](https://raw.githubusercontent.com/Te-k/stalkerware-indicators/master/stalkerware.stix2).
|
||||
|
||||
You can automaticallly download the latest public indicator files with the command `mvt-ios download-iocs` or `mvt-android download-iocs`.
|
||||
You can automaticallly download the latest public indicator files with the command `mvt-ios download-iocs` or `mvt-android download-iocs`. These commands download the list of indicators listed [here](https://github.com/mvt-project/mvt/blob/main/public_indicators.json) and store them in the [appdir](https://pypi.org/project/appdirs/) folder. They are then loaded automatically by mvt.
|
||||
|
||||
Please [open an issue](https://github.com/mvt-project/mvt/issues/) to suggest new sources of STIX-formatted IOCs.
|
||||
|
||||
@@ -89,10 +89,6 @@ class Files(AndroidExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
if self.indicators.check_file_name(result["path"]):
|
||||
self.log.warning("Found a known suspicous filename at path: \"%s\"", result["path"])
|
||||
self.detected.append(result)
|
||||
|
||||
if self.indicators.check_file_path(result["path"]):
|
||||
self.log.warning("Found a known suspicous file at path: \"%s\"", result["path"])
|
||||
self.detected.append(result)
|
||||
|
||||
@@ -25,6 +25,7 @@ class Indicators:
|
||||
self.ioc_processes = []
|
||||
self.ioc_emails = []
|
||||
self.ioc_files = []
|
||||
self.ioc_file_paths = []
|
||||
self.ioc_files_sha256 = []
|
||||
self.ioc_app_ids = []
|
||||
self.ios_profile_ids = []
|
||||
@@ -109,6 +110,9 @@ class Indicators:
|
||||
elif key == "file:name":
|
||||
self._add_indicator(ioc=value,
|
||||
iocs_list=self.ioc_files)
|
||||
elif key == "file:path":
|
||||
self._add_indicator(ioc=value,
|
||||
iocs_list=self.ioc_file_paths)
|
||||
elif key == "app:id":
|
||||
self._add_indicator(ioc=value,
|
||||
iocs_list=self.ioc_app_ids)
|
||||
@@ -272,30 +276,26 @@ class Indicators:
|
||||
|
||||
return False
|
||||
|
||||
def check_file_name(self, file_path) -> bool:
|
||||
"""Check the provided file path against the list of file indicators.
|
||||
def check_file_name(self, file_name) -> bool:
|
||||
"""Check the provided file name against the list of file indicators.
|
||||
|
||||
:param file_path: File path or file name to check against file
|
||||
:param file_name: File name to check against file
|
||||
indicators
|
||||
:type file_path: str
|
||||
:returns: True if the file path matched an indicator, otherwise False
|
||||
:type file_name: str
|
||||
:returns: True if the file name matched an indicator, otherwise False
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if not file_path:
|
||||
if not file_name:
|
||||
return False
|
||||
|
||||
file_name = os.path.basename(file_path)
|
||||
if file_name in self.ioc_files:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# TODO: The difference between check_file_name() and check_file_path()
|
||||
# needs to be more explicit and clear. Probably, the two should just
|
||||
# be combined into one function.
|
||||
def check_file_path(self, file_path) -> bool:
|
||||
"""Check the provided file path against the list of file indicators.
|
||||
"""Check the provided file path against the list of file indicators (both path and name).
|
||||
|
||||
:param file_path: File path or file name to check against file
|
||||
indicators
|
||||
@@ -307,7 +307,10 @@ class Indicators:
|
||||
if not file_path:
|
||||
return False
|
||||
|
||||
for ioc_file in self.ioc_files:
|
||||
if self.check_file_name(os.path.basename(file_path)):
|
||||
return True
|
||||
|
||||
for ioc_file in self.ioc_file_paths:
|
||||
# Strip any trailing slash from indicator paths to match directories.
|
||||
if file_path.startswith(ioc_file.rstrip("/")):
|
||||
return True
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import requests
|
||||
from packaging import version
|
||||
|
||||
MVT_VERSION = "1.4.4"
|
||||
MVT_VERSION = "1.4.5"
|
||||
|
||||
|
||||
def check_for_updates():
|
||||
|
||||
@@ -83,7 +83,7 @@ class Manifest(IOSExtraction):
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if self.indicators.check_file_name(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)
|
||||
continue
|
||||
|
||||
@@ -37,10 +37,6 @@ class Filesystem(IOSExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
if self.indicators.check_file(result["path"]):
|
||||
self.log.warning("Found a known malicious file name at path: %s", result["path"])
|
||||
self.detected.append(result)
|
||||
|
||||
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)
|
||||
|
||||
@@ -34,12 +34,19 @@ class ShutdownLog(IOSExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
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)
|
||||
continue
|
||||
|
||||
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)
|
||||
continue
|
||||
|
||||
def process_shutdownlog(self, content):
|
||||
current_processes = []
|
||||
|
||||
@@ -41,13 +41,13 @@ class LocationdClients(IOSExtraction):
|
||||
|
||||
def serialize(self, record):
|
||||
records = []
|
||||
for ts in self.timestamps:
|
||||
if ts in record.keys():
|
||||
for timestamp in self.timestamps:
|
||||
if timestamp in record.keys():
|
||||
records.append({
|
||||
"timestamp": record[ts],
|
||||
"timestamp": record[timestamp],
|
||||
"module": self.__class__.__name__,
|
||||
"event": ts,
|
||||
"data": f"{ts} from {record['package']}"
|
||||
"event": timestamp,
|
||||
"data": f"{timestamp} from {record['package']}"
|
||||
})
|
||||
|
||||
return records
|
||||
@@ -61,7 +61,31 @@ class LocationdClients(IOSExtraction):
|
||||
proc_name = parts[len(parts)-1]
|
||||
|
||||
if self.indicators.check_process(proc_name):
|
||||
self.log.warning("Found a suspicious process name in LocationD entry %s",
|
||||
result["package"])
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if "BundlePath" in result:
|
||||
if self.indicators.check_file_path(result["BundlePath"]):
|
||||
self.log.warning("Found a suspicious file path in Location D: %s",
|
||||
result["BundlePath"])
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if "Executable" in result:
|
||||
if self.indicators.check_file_path(result["Executable"]):
|
||||
self.log.warning("Found a suspicious file path in Location D: %s",
|
||||
result["Executable"])
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if "Registered" in result:
|
||||
if self.indicators.check_file_path(result["Registered"]):
|
||||
self.log.warning("Found a suspicious file path in Location D: %s",
|
||||
result["Registered"])
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
def _extract_locationd_entries(self, file_path):
|
||||
with open(file_path, "rb") as handle:
|
||||
|
||||
@@ -66,6 +66,15 @@ class TCC(IOSExtraction):
|
||||
"data": msg
|
||||
}
|
||||
|
||||
def check_indicators(self):
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
if self.indicators.check_process(result["client"]):
|
||||
self.log.warning("Found malicious process in TCC database: %s", result["client"])
|
||||
self.detected.append(result)
|
||||
|
||||
def process_db(self, file_path):
|
||||
conn = sqlite3.connect(file_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
Binary file not shown.
@@ -5,27 +5,32 @@
|
||||
|
||||
import logging
|
||||
|
||||
from mvt.common.indicators import Indicators
|
||||
from mvt.common.module import run_module
|
||||
from mvt.ios.modules.mixed.tcc import TCC
|
||||
|
||||
from ..utils import get_backup_folder
|
||||
|
||||
|
||||
class TestManifestModule:
|
||||
def test_manifest(self):
|
||||
class TestTCCtModule:
|
||||
def test_tcc(self):
|
||||
m = TCC(base_folder=get_backup_folder(), log=logging, results=[])
|
||||
run_module(m)
|
||||
assert len(m.results) == 11
|
||||
assert len(m.timeline) == 11
|
||||
assert len(m.detected) == 0
|
||||
assert m.results[0]["service"] == "kTCCServiceUbiquity"
|
||||
assert m.results[0]["client"] == "com.apple.Preferences"
|
||||
assert m.results[0]["auth_value"] == "allowed"
|
||||
|
||||
def test_manifest_2(self):
|
||||
def test_tcc_detection(self, indicator_file):
|
||||
m = TCC(base_folder=get_backup_folder(), log=logging, results=[])
|
||||
ind = Indicators(log=logging)
|
||||
ind.parse_stix2(indicator_file)
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 11
|
||||
assert len(m.timeline) == 11
|
||||
assert len(m.detected) == 0
|
||||
assert m.results[0]["service"] == "kTCCServiceUbiquity"
|
||||
assert m.results[0]["auth_value"] == "allowed"
|
||||
assert len(m.detected) == 1
|
||||
assert m.detected[0]["service"] == "kTCCServiceLiverpool"
|
||||
assert m.detected[0]["client"] == "Launch"
|
||||
|
||||
Reference in New Issue
Block a user