Compare commits

...
Author SHA1 Message Date
Janik Besendorf b7ff933213 Rename from_folder to from_dir in tests 2025-10-31 13:44:58 +01:00
Janik Besendorf 802ab11deb Fix imports and remove duplicate hashes param 2025-10-31 13:29:16 +01:00
besendorf 8cb75d9517 Merge branch 'main' into feature/deduplicate-adb-aqf-modules 2025-10-31 12:11:17 +01:00
Donncha Ó Cearbhaill 67863d8f8d Merge branch 'main' into feature/deduplicate-adb-aqf-modules 2025-10-21 15:14:26 +02:00
Janik Besendorf 5bf71e2268 add missing import 2025-10-05 18:13:31 +02:00
besendorf caaffc8988 Merge branch 'main' into feature/deduplicate-adb-aqf-modules 2025-10-05 18:04:01 +02:00
Donncha Ó Cearbhaill 4c1cdf5129 Raise the proper NoAndroidQFBackup exception when a back-up isn't found 2025-02-11 15:04:48 +01:00
Donncha Ó Cearbhaill a08c24b02a Deduplicate modules which are run by the sub-commands. 2025-02-10 20:32:51 +01:00
Donncha Ó Cearbhaill 5d696350dc Run bugreport and backup modules during check-androidqf
Adding support to automatically run ADB backup and bugreport modules
automatically when running the check-androidqf command. This is a first
step to deduplicate the code for Android modules.
2025-02-10 19:28:20 +01:00
71 changed files with 363 additions and 1302 deletions

No files matched your search

+7
View File
@@ -7,6 +7,7 @@ import logging
from typing import Optional from typing import Optional
from mvt.common.command import Command from mvt.common.command import Command
from mvt.common.indicators import Indicators
from .modules.adb import ADB_MODULES from .modules.adb import ADB_MODULES
@@ -19,9 +20,12 @@ class CmdAndroidCheckADB(Command):
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: Optional[bool] = False,
sub_command: Optional[bool] = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -29,9 +33,12 @@ class CmdAndroidCheckADB(Command):
target_path=target_path, target_path=target_path,
results_path=results_path, results_path=results_path,
ioc_files=ioc_files, ioc_files=ioc_files,
iocs=iocs,
module_name=module_name, module_name=module_name,
serial=serial, serial=serial,
module_options=module_options, module_options=module_options,
hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
+136 -13
View File
@@ -9,23 +9,41 @@ import zipfile
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
from mvt.android.cmd_check_backup import CmdAndroidCheckBackup
from mvt.android.cmd_check_bugreport import CmdAndroidCheckBugreport
from mvt.common.command import Command from mvt.common.command import Command
from mvt.common.indicators import Indicators
from .modules.androidqf import ANDROIDQF_MODULES from .modules.androidqf import ANDROIDQF_MODULES
from .modules.androidqf.base import AndroidQFModule
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class NoAndroidQFTargetPath(Exception):
pass
class NoAndroidQFBugReport(Exception):
pass
class NoAndroidQFBackup(Exception):
pass
class CmdAndroidCheckAndroidQF(Command): class CmdAndroidCheckAndroidQF(Command):
def __init__( def __init__(
self, self,
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: bool = False, hashes: Optional[bool] = False,
sub_command: Optional[bool] = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -33,10 +51,12 @@ class CmdAndroidCheckAndroidQF(Command):
target_path=target_path, target_path=target_path,
results_path=results_path, results_path=results_path,
ioc_files=ioc_files, ioc_files=ioc_files,
iocs=iocs,
module_name=module_name, module_name=module_name,
serial=serial, serial=serial,
module_options=module_options, module_options=module_options,
hashes=hashes, hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
@@ -45,27 +65,130 @@ class CmdAndroidCheckAndroidQF(Command):
self.name = "check-androidqf" self.name = "check-androidqf"
self.modules = ANDROIDQF_MODULES self.modules = ANDROIDQF_MODULES
self.format: Optional[str] = None self.__format: Optional[str] = None
self.archive: Optional[zipfile.ZipFile] = None self.__zip: Optional[zipfile.ZipFile] = None
self.files: List[str] = [] self.__files: List[str] = []
def init(self): def init(self):
if os.path.isdir(self.target_path): if os.path.isdir(self.target_path):
self.format = "dir" self.__format = "dir"
parent_path = Path(self.target_path).absolute().parent.as_posix() parent_path = Path(self.target_path).absolute().parent.as_posix()
target_abs_path = os.path.abspath(self.target_path) target_abs_path = os.path.abspath(self.target_path)
for root, subdirs, subfiles in os.walk(target_abs_path): for root, subdirs, subfiles in os.walk(target_abs_path):
for fname in subfiles: for fname in subfiles:
file_path = os.path.relpath(os.path.join(root, fname), parent_path) file_path = os.path.relpath(os.path.join(root, fname), parent_path)
self.files.append(file_path) self.__files.append(file_path)
elif os.path.isfile(self.target_path): elif os.path.isfile(self.target_path):
self.format = "zip" self.__format = "zip"
self.archive = zipfile.ZipFile(self.target_path) self.__zip = zipfile.ZipFile(self.target_path)
self.files = self.archive.namelist() self.__files = self.__zip.namelist()
def module_init(self, module): def module_init(self, module: AndroidQFModule) -> None: # type: ignore[override]
if self.format == "zip": if self.__format == "zip" and self.__zip:
module.from_zip_file(self.archive, self.files) module.from_zip(self.__zip, self.__files)
return
if not self.target_path:
raise NoAndroidQFTargetPath
parent_path = Path(self.target_path).absolute().parent.as_posix()
module.from_dir(parent_path, self.__files)
def load_bugreport(self) -> zipfile.ZipFile:
bugreport_zip_path = None
for file_name in self.__files:
if file_name.endswith("bugreport.zip"):
bugreport_zip_path = file_name
break
else: else:
raise NoAndroidQFBugReport
if self.__format == "zip" and self.__zip:
handle = self.__zip.open(bugreport_zip_path)
return zipfile.ZipFile(handle)
if self.__format == "dir" and self.target_path:
parent_path = Path(self.target_path).absolute().parent.as_posix() parent_path = Path(self.target_path).absolute().parent.as_posix()
module.from_folder(parent_path, self.files) bug_report_path = os.path.join(parent_path, bugreport_zip_path)
return zipfile.ZipFile(bug_report_path)
raise NoAndroidQFBugReport
def load_backup(self) -> bytes:
backup_ab_path = None
for file_name in self.__files:
if file_name.endswith("backup.ab"):
backup_ab_path = file_name
break
else:
raise NoAndroidQFBackup
if self.__format == "zip" and self.__zip:
backup_file_handle = self.__zip.open(backup_ab_path)
return backup_file_handle.read()
if self.__format == "dir" and self.target_path:
parent_path = Path(self.target_path).absolute().parent.as_posix()
backup_path = os.path.join(parent_path, backup_ab_path)
with open(backup_path, "rb") as backup_file:
backup_ab_data = backup_file.read()
return backup_ab_data
raise NoAndroidQFBackup
def run_bugreport_cmd(self) -> bool:
try:
bugreport = self.load_bugreport()
except NoAndroidQFBugReport:
self.log.warning(
"Skipping bugreport modules as no bugreport.zip found in AndroidQF data."
)
return False
else:
cmd = CmdAndroidCheckBugreport(
target_path=None,
results_path=self.results_path,
ioc_files=self.ioc_files,
iocs=self.iocs,
module_options=self.module_options,
hashes=self.hashes,
sub_command=True,
)
cmd.from_zip(bugreport)
cmd.run()
self.detected_count += cmd.detected_count
self.timeline.extend(cmd.timeline)
self.timeline_detected.extend(cmd.timeline_detected)
def run_backup_cmd(self) -> bool:
try:
backup = self.load_backup()
except NoAndroidQFBackup:
self.log.warning(
"Skipping backup modules as no backup.ab found in AndroidQF data."
)
return False
else:
cmd = CmdAndroidCheckBackup(
target_path=None,
results_path=self.results_path,
ioc_files=self.ioc_files,
iocs=self.iocs,
module_options=self.module_options,
hashes=self.hashes,
sub_command=True,
)
cmd.from_ab(backup)
cmd.run()
self.detected_count += cmd.detected_count
self.timeline.extend(cmd.timeline)
self.timeline_detected.extend(cmd.timeline_detected)
def finish(self) -> None:
"""
Run the bugreport and backup modules if the respective files are found in the AndroidQF data.
"""
self.run_bugreport_cmd()
self.run_backup_cmd()
+37 -31
View File
@@ -20,6 +20,7 @@ from mvt.android.parsers.backup import (
parse_backup_file, parse_backup_file,
) )
from mvt.common.command import Command from mvt.common.command import Command
from mvt.common.indicators import Indicators
from .modules.backup import BACKUP_MODULES from .modules.backup import BACKUP_MODULES
@@ -32,10 +33,12 @@ class CmdAndroidCheckBackup(Command):
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: bool = False, hashes: Optional[bool] = False,
sub_command: Optional[bool] = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -43,10 +46,12 @@ class CmdAndroidCheckBackup(Command):
target_path=target_path, target_path=target_path,
results_path=results_path, results_path=results_path,
ioc_files=ioc_files, ioc_files=ioc_files,
iocs=iocs,
module_name=module_name, module_name=module_name,
serial=serial, serial=serial,
module_options=module_options, module_options=module_options,
hashes=hashes, hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
@@ -59,6 +64,34 @@ class CmdAndroidCheckBackup(Command):
self.backup_archive: Optional[tarfile.TarFile] = None self.backup_archive: Optional[tarfile.TarFile] = None
self.backup_files: List[str] = [] self.backup_files: List[str] = []
def from_ab(self, ab_file_bytes: bytes) -> None:
self.backup_type = "ab"
header = parse_ab_header(ab_file_bytes)
if not header["backup"]:
log.critical("Invalid backup format, file should be in .ab format")
sys.exit(1)
password = None
if header["encryption"] != "none":
password = prompt_or_load_android_backup_password(log, self.module_options)
if not password:
log.critical("No backup password provided.")
sys.exit(1)
try:
tardata = parse_backup_file(ab_file_bytes, password=password)
except InvalidBackupPassword:
log.critical("Invalid backup password")
sys.exit(1)
except AndroidBackupParsingError as exc:
log.critical("Impossible to parse this backup file: %s", exc)
log.critical("Please use Android Backup Extractor (ABE) instead")
sys.exit(1)
dbytes = io.BytesIO(tardata)
self.backup_archive = tarfile.open(fileobj=dbytes)
for member in self.backup_archive:
self.backup_files.append(member.name)
def init(self) -> None: def init(self) -> None:
if not self.target_path: if not self.target_path:
return return
@@ -66,35 +99,8 @@ class CmdAndroidCheckBackup(Command):
if os.path.isfile(self.target_path): if os.path.isfile(self.target_path):
self.backup_type = "ab" self.backup_type = "ab"
with open(self.target_path, "rb") as handle: with open(self.target_path, "rb") as handle:
data = handle.read() ab_file_bytes = handle.read()
self.from_ab(ab_file_bytes)
header = parse_ab_header(data)
if not header["backup"]:
log.critical("Invalid backup format, file should be in .ab format")
sys.exit(1)
password = None
if header["encryption"] != "none":
password = prompt_or_load_android_backup_password(
log, self.module_options
)
if not password:
log.critical("No backup password provided.")
sys.exit(1)
try:
tardata = parse_backup_file(data, password=password)
except InvalidBackupPassword:
log.critical("Invalid backup password")
sys.exit(1)
except AndroidBackupParsingError as exc:
log.critical("Impossible to parse this backup file: %s", exc)
log.critical("Please use Android Backup Extractor (ABE) instead")
sys.exit(1)
dbytes = io.BytesIO(tardata)
self.backup_archive = tarfile.open(fileobj=dbytes)
for member in self.backup_archive:
self.backup_files.append(member.name)
elif os.path.isdir(self.target_path): elif os.path.isdir(self.target_path):
self.backup_type = "folder" self.backup_type = "folder"
@@ -113,6 +119,6 @@ class CmdAndroidCheckBackup(Command):
def module_init(self, module: BackupExtraction) -> None: # type: ignore[override] def module_init(self, module: BackupExtraction) -> None: # type: ignore[override]
if self.backup_type == "folder": if self.backup_type == "folder":
module.from_folder(self.target_path, self.backup_files) module.from_dir(self.target_path, self.backup_files)
else: else:
module.from_ab(self.target_path, self.backup_archive, self.backup_files) module.from_ab(self.target_path, self.backup_archive, self.backup_files)
+44 -21
View File
@@ -11,6 +11,7 @@ from zipfile import ZipFile
from mvt.android.modules.bugreport.base import BugReportModule from mvt.android.modules.bugreport.base import BugReportModule
from mvt.common.command import Command from mvt.common.command import Command
from mvt.common.indicators import Indicators
from .modules.bugreport import BUGREPORT_MODULES from .modules.bugreport import BUGREPORT_MODULES
@@ -23,10 +24,12 @@ class CmdAndroidCheckBugreport(Command):
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: bool = False, hashes: Optional[bool] = False,
sub_command: Optional[bool] = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -34,10 +37,12 @@ class CmdAndroidCheckBugreport(Command):
target_path=target_path, target_path=target_path,
results_path=results_path, results_path=results_path,
ioc_files=ioc_files, ioc_files=ioc_files,
iocs=iocs,
module_name=module_name, module_name=module_name,
serial=serial, serial=serial,
module_options=module_options, module_options=module_options,
hashes=hashes, hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
@@ -46,35 +51,53 @@ class CmdAndroidCheckBugreport(Command):
self.name = "check-bugreport" self.name = "check-bugreport"
self.modules = BUGREPORT_MODULES self.modules = BUGREPORT_MODULES
self.bugreport_format: str = "" self.__format: str = ""
self.bugreport_archive: Optional[ZipFile] = None self.__zip: Optional[ZipFile] = None
self.bugreport_files: List[str] = [] self.__files: List[str] = []
def from_dir(self, dir_path: str) -> None:
"""This method is used to initialize the bug report analysis from an
uncompressed directory.
"""
self.__format = "dir"
self.target_path = dir_path
parent_path = Path(dir_path).absolute().as_posix()
for root, _, subfiles in os.walk(os.path.abspath(dir_path)):
for file_name in subfiles:
file_path = os.path.relpath(os.path.join(root, file_name), parent_path)
self.__files.append(file_path)
def from_zip(self, bugreport_zip: ZipFile) -> None:
"""This method is used to initialize the bug report analysis from a
compressed archive.
"""
# NOTE: This will be invoked either by the CLI directly,or by the
# check-androidqf command. We need this because we want to support
# check-androidqf to analyse compressed archives itself too.
# So, we'll need to extract bugreport.zip from a 'androidqf.zip', and
# since nothing is written on disk, we need to be able to pass this
# command a ZipFile instance in memory.
self.__format = "zip"
self.__zip = bugreport_zip
for file_name in self.__zip.namelist():
self.__files.append(file_name)
def init(self) -> None: def init(self) -> None:
if not self.target_path: if not self.target_path:
return return
if os.path.isfile(self.target_path): if os.path.isfile(self.target_path):
self.bugreport_format = "zip" self.from_zip(ZipFile(self.target_path))
self.bugreport_archive = ZipFile(self.target_path)
for file_name in self.bugreport_archive.namelist():
self.bugreport_files.append(file_name)
elif os.path.isdir(self.target_path): elif os.path.isdir(self.target_path):
self.bugreport_format = "dir" self.from_dir(self.target_path)
parent_path = Path(self.target_path).absolute().as_posix()
for root, _, subfiles in os.walk(os.path.abspath(self.target_path)):
for file_name in subfiles:
file_path = os.path.relpath(
os.path.join(root, file_name), parent_path
)
self.bugreport_files.append(file_path)
def module_init(self, module: BugReportModule) -> None: # type: ignore[override] def module_init(self, module: BugReportModule) -> None: # type: ignore[override]
if self.bugreport_format == "zip": if self.__format == "zip":
module.from_zip(self.bugreport_archive, self.bugreport_files) module.from_zip(self.__zip, self.__files)
else: else:
module.from_folder(self.target_path, self.bugreport_files) module.from_dir(self.target_path, self.__files)
def finish(self) -> None: def finish(self) -> None:
if self.bugreport_archive: if self.__zip:
self.bugreport_archive.close() self.__zip.close()
-16
View File
@@ -4,15 +4,7 @@
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
from .chrome_history import ChromeHistory from .chrome_history import ChromeHistory
from .dumpsys_accessibility import DumpsysAccessibility
from .dumpsys_activities import DumpsysActivities
from .dumpsys_appops import DumpsysAppOps
from .dumpsys_battery_daily import DumpsysBatteryDaily
from .dumpsys_battery_history import DumpsysBatteryHistory
from .dumpsys_dbinfo import DumpsysDBInfo
from .dumpsys_adbstate import DumpsysADBState
from .dumpsys_full import DumpsysFull from .dumpsys_full import DumpsysFull
from .dumpsys_receivers import DumpsysReceivers
from .files import Files from .files import Files
from .getprop import Getprop from .getprop import Getprop
from .logcat import Logcat from .logcat import Logcat
@@ -32,15 +24,7 @@ ADB_MODULES = [
Getprop, Getprop,
Settings, Settings,
SELinuxStatus, SELinuxStatus,
DumpsysBatteryHistory,
DumpsysBatteryDaily,
DumpsysReceivers,
DumpsysActivities,
DumpsysAccessibility,
DumpsysDBInfo,
DumpsysADBState,
DumpsysFull, DumpsysFull,
DumpsysAppOps,
Packages, Packages,
Logcat, Logcat,
RootBinaries, RootBinaries,
@@ -1,49 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
from .base import AndroidExtraction
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidExtraction):
"""This module extracts stats on accessibility."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys accessibility")
self._adb_disconnect()
self.parse(output)
for result in self.results:
self.log.info(
'Found installed accessibility service "%s"', result.get("service")
)
self.log.info(
"Identified a total of %d accessibility services", len(self.results)
)
@@ -1,45 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_package_activities import (
DumpsysPackageActivitiesArtifact,
)
from .base import AndroidExtraction
class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidExtraction):
"""This module extracts details on receivers for risky activities."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
self.results = results if results else []
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys package")
self._adb_disconnect()
self.parse(output)
self.log.info("Extracted %d package activities", len(self.results))
@@ -1,45 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_adb import DumpsysADBArtifact
from .base import AndroidExtraction
class DumpsysADBState(DumpsysADBArtifact, AndroidExtraction):
"""This module extracts ADB keystore state."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys adb", decode=False)
self._adb_disconnect()
self.parse(output)
if self.results:
self.log.info(
"Identified a total of %d trusted ADB keys",
len(self.results[0].get("user_keys", [])),
)
@@ -1,46 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
from .base import AndroidExtraction
class DumpsysAppOps(DumpsysAppopsArtifact, AndroidExtraction):
"""This module extracts records from App-op Manager."""
slug = "dumpsys_appops"
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys appops")
self._adb_disconnect()
self.parse(output)
self.log.info(
"Extracted a total of %d records from app-ops manager", len(self.results)
)
@@ -1,44 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from .base import AndroidExtraction
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidExtraction):
"""This module extracts records from battery daily updates."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys batterystats --daily")
self._adb_disconnect()
self.parse(output)
self.log.info(
"Extracted %d records from battery daily stats", len(self.results)
)
@@ -1,42 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
from .base import AndroidExtraction
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidExtraction):
"""This module extracts records from battery history events."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys batterystats --history")
self._adb_disconnect()
self.parse(output)
self.log.info("Extracted %d records from battery history", len(self.results))
@@ -1,47 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact
from .base import AndroidExtraction
class DumpsysDBInfo(DumpsysDBInfoArtifact, AndroidExtraction):
"""This module extracts records from battery daily updates."""
slug = "dumpsys_dbinfo"
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys dbinfo")
self._adb_disconnect()
self.parse(output)
self.log.info(
"Extracted a total of %d records from database information",
len(self.results),
)
@@ -1,44 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
from .base import AndroidExtraction
class DumpsysReceivers(DumpsysReceiversArtifact, AndroidExtraction):
"""This module extracts details on receivers for risky activities."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
self.results = results if results else {}
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys package")
self.parse(output)
self._adb_disconnect()
self.log.info("Extracted receivers for %d intents", len(self.results))
+12 -32
View File
@@ -3,42 +3,22 @@
# 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/
from .dumpsys_accessibility import DumpsysAccessibility from .aqf_files import AQFFiles
from .dumpsys_activities import DumpsysActivities from .aqf_getprop import AQFGetProp
from .dumpsys_appops import DumpsysAppops from .aqf_packages import AQFPackages
from .dumpsys_battery_daily import DumpsysBatteryDaily from .aqf_processes import AQFProcesses
from .dumpsys_battery_history import DumpsysBatteryHistory from .aqf_settings import AQFSettings
from .dumpsys_dbinfo import DumpsysDBInfo
from .dumpsys_packages import DumpsysPackages
from .dumpsys_receivers import DumpsysReceivers
from .dumpsys_adb import DumpsysADBState
from .getprop import Getprop
from .packages import Packages
from .dumpsys_platform_compat import DumpsysPlatformCompat
from .processes import Processes
from .settings import Settings
from .sms import SMS
from .files import Files
from .root_binaries import RootBinaries
from .mounts import Mounts from .mounts import Mounts
from .root_binaries import RootBinaries
from .sms import SMS
ANDROIDQF_MODULES = [ ANDROIDQF_MODULES = [
DumpsysActivities, AQFPackages,
DumpsysReceivers, AQFProcesses,
DumpsysAccessibility, AQFGetProp,
DumpsysAppops, AQFSettings,
DumpsysDBInfo, AQFFiles,
DumpsysBatteryDaily,
DumpsysBatteryHistory,
DumpsysADBState,
Packages,
DumpsysPlatformCompat,
Processes,
Getprop,
Settings,
SMS, SMS,
DumpsysPackages,
Files,
RootBinaries, RootBinaries,
Mounts, Mounts,
] ]
@@ -21,8 +21,13 @@ SUSPICIOUS_PATHS = [
] ]
class Files(AndroidQFModule): class AQFFiles(AndroidQFModule):
"""This module analyse list of files""" """
This module analyzes the files.json dump generated by AndroidQF.
The format needs to be kept in sync with the AndroidQF module code.
https://github.com/mvt-project/androidqf/blob/main/android-collector/cmd/find.go#L28
"""
def __init__( def __init__(
self, self,
@@ -11,7 +11,7 @@ from mvt.android.artifacts.getprop import GetProp as GetPropArtifact
from .base import AndroidQFModule from .base import AndroidQFModule
class Getprop(GetPropArtifact, AndroidQFModule): class AQFGetProp(GetPropArtifact, AndroidQFModule):
"""This module extracts data from get properties.""" """This module extracts data from get properties."""
def __init__( def __init__(
@@ -13,10 +13,10 @@ from .base import AndroidQFModule
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
class LogsFileTimestamps(FileTimestampsArtifact, AndroidQFModule): class AQFLogTimestamps(FileTimestampsArtifact, AndroidQFModule):
"""This module extracts records from battery daily updates.""" """This module creates timeline for log files extracted by AQF."""
slug = "logfile_timestamps" slug = "aqf_log_timestamps"
def __init__( def __init__(
self, self,
@@ -19,7 +19,7 @@ from mvt.android.utils import (
from .base import AndroidQFModule from .base import AndroidQFModule
class Packages(AndroidQFModule): class AQFPackages(AndroidQFModule):
"""This module examines the installed packages in packages.json""" """This module examines the installed packages in packages.json"""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.processes import Processes as ProcessesArtifact
from .base import AndroidQFModule from .base import AndroidQFModule
class Processes(ProcessesArtifact, AndroidQFModule): class AQFProcesses(ProcessesArtifact, AndroidQFModule):
"""This module analyse running processes""" """This module analyse running processes"""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.settings import Settings as SettingsArtifact
from .base import AndroidQFModule from .base import AndroidQFModule
class Settings(SettingsArtifact, AndroidQFModule): class AQFSettings(SettingsArtifact, AndroidQFModule):
"""This module analyse setting files""" """This module analyse setting files"""
def __init__( def __init__(
+2 -2
View File
@@ -37,11 +37,11 @@ class AndroidQFModule(MVTModule):
self.files: List[str] = [] self.files: List[str] = []
self.archive: Optional[zipfile.ZipFile] = None self.archive: Optional[zipfile.ZipFile] = None
def from_folder(self, parent_path: str, files: List[str]): def from_dir(self, parent_path: str, files: List[str]) -> None:
self.parent_path = parent_path self.parent_path = parent_path
self.files = files self.files = files
def from_zip_file(self, archive: zipfile.ZipFile, files: List[str]): def from_zip(self, archive: zipfile.ZipFile, files: List[str]) -> None:
self.archive = archive self.archive = archive
self.files = files self.files = files
@@ -1,51 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
from .base import AndroidQFModule
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidQFModule):
"""This module analyses dumpsys accessibility"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE accessibility:")
self.parse(content)
for result in self.results:
self.log.info(
'Found installed accessibility service "%s"', result.get("service")
)
self.log.info(
"Identified a total of %d accessibility services", len(self.results)
)
@@ -1,50 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_package_activities import (
DumpsysPackageActivitiesArtifact,
)
from .base import AndroidQFModule
class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidQFModule):
"""This module extracts details on receivers for risky activities."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
self.results = results if results else []
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
# Get data and extract the dumpsys section
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
# Parse it
self.parse(content)
self.log.info("Extracted %d package activities", len(self.results))
@@ -1,51 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_adb import DumpsysADBArtifact
from .base import AndroidQFModule
class DumpsysADBState(DumpsysADBArtifact, AndroidQFModule):
"""This module extracts ADB keystore state."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
full_dumpsys = self._get_file_content(dumpsys_file[0])
content = self.extract_dumpsys_section(
full_dumpsys,
b"DUMP OF SERVICE adb:",
binary=True,
)
self.parse(content)
if self.results:
self.log.info(
"Identified a total of %d trusted ADB keys",
len(self.results[0].get("user_keys", [])),
)
@@ -1,46 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
from .base import AndroidQFModule
class DumpsysAppops(DumpsysAppopsArtifact, AndroidQFModule):
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
# Extract section
data = self._get_file_content(dumpsys_file[0])
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE appops:"
)
# Parse it
self.parse(section)
self.log.info("Identified %d applications in AppOps Manager", len(self.results))
@@ -1,46 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from .base import AndroidQFModule
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidQFModule):
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
# Extract section
data = self._get_file_content(dumpsys_file[0])
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
# Parse it
self.parse(section)
self.log.info("Extracted a total of %d battery daily stats", len(self.results))
@@ -1,46 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
from .base import AndroidQFModule
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidQFModule):
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
# Extract section
data = self._get_file_content(dumpsys_file[0])
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
# Parse it
self.parse(section)
self.log.info("Extracted a total of %d battery daily stats", len(self.results))
@@ -1,46 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact
from .base import AndroidQFModule
class DumpsysDBInfo(DumpsysDBInfoArtifact, AndroidQFModule):
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
# Extract dumpsys DBInfo section
data = self._get_file_content(dumpsys_file[0])
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE dbinfo:"
)
# Parse it
self.parse(section)
self.log.info("Identified %d DB Info entries", len(self.results))
@@ -1,62 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Any, Dict, List, Optional
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
from mvt.android.modules.adb.packages import (
DANGEROUS_PERMISSIONS,
DANGEROUS_PERMISSIONS_THRESHOLD,
)
from .base import AndroidQFModule
class DumpsysPackages(DumpsysPackagesArtifact, AndroidQFModule):
"""This module analyse dumpsys packages"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[List[Dict[str, Any]]] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if len(dumpsys_file) != 1:
self.log.info("Dumpsys file not found")
return
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
self.parse(content)
for result in self.results:
dangerous_permissions_count = 0
for perm in result["permissions"]:
if perm["name"] in DANGEROUS_PERMISSIONS:
dangerous_permissions_count += 1
if dangerous_permissions_count >= DANGEROUS_PERMISSIONS_THRESHOLD:
self.log.info(
'Found package "%s" requested %d potentially dangerous permissions',
result["package_name"],
dangerous_permissions_count,
)
self.log.info("Extracted details on %d packages", len(self.results))
@@ -1,44 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Optional
from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatArtifact
from .base import AndroidQFModule
class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, AndroidQFModule):
"""This module extracts details on uninstalled apps."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:")
self.parse(content)
self.log.info("Found %d uninstalled apps", len(self.results))
@@ -1,49 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from typing import Any, Dict, List, Optional, Union
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
from .base import AndroidQFModule
class DumpsysReceivers(DumpsysReceiversArtifact, AndroidQFModule):
"""This module analyse dumpsys receivers"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Union[List[Any], Dict[str, Any], None] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
self.results = results if results else {}
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
data = self._get_file_content(dumpsys_file[0])
dumpsys_section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE package:"
)
self.parse(dumpsys_section)
self.log.info("Extracted receivers for %d intents", len(self.results))
+7 -1
View File
@@ -19,7 +19,13 @@ from .base import AndroidQFModule
class SMS(AndroidQFModule): class SMS(AndroidQFModule):
"""This module analyse SMS file in backup""" """
This module analyse SMS file in backup
XXX: We should also de-duplicate this AQF module, but first we
need to add tests for loading encrypted SMS backups through the backup
sub-module.
"""
def __init__( def __init__(
self, self,
+5 -6
View File
@@ -37,10 +37,7 @@ class BackupExtraction(MVTModule):
self.tar = None self.tar = None
self.files = [] self.files = []
def from_folder(self, backup_path: Optional[str], files: List[str]) -> None: def from_dir(self, backup_path: Optional[str], files: List[str]) -> None:
"""
Get all the files and list them
"""
self.backup_path = backup_path self.backup_path = backup_path
self.files = files self.files = files
@@ -58,14 +55,16 @@ class BackupExtraction(MVTModule):
return fnmatch.filter(self.files, pattern) return fnmatch.filter(self.files, pattern)
def _get_file_content(self, file_path: str) -> bytes: def _get_file_content(self, file_path: str) -> bytes:
if self.ab: if self.tar:
try: try:
member = self.tar.getmember(file_path) member = self.tar.getmember(file_path)
except KeyError: except KeyError:
return None return None
handle = self.tar.extractfile(member) handle = self.tar.extractfile(member)
else: elif self.backup_path:
handle = open(os.path.join(self.backup_path, file_path), "rb") handle = open(os.path.join(self.backup_path, file_path), "rb")
else:
raise ValueError("No backup path or tar file provided")
data = handle.read() data = handle.read()
handle.close() handle.close()
+2 -2
View File
@@ -50,13 +50,13 @@ class SMS(BackupExtraction):
def run(self) -> None: def run(self) -> None:
sms_path = "apps/com.android.providers.telephony/d_f/*_sms_backup" sms_path = "apps/com.android.providers.telephony/d_f/*_sms_backup"
for file in self._get_files_by_pattern(sms_path): for file in self._get_files_by_pattern(sms_path):
self.log.info("Processing SMS backup file at %s", file) self.log.debug("Processing SMS backup file at %s", file)
data = self._get_file_content(file) data = self._get_file_content(file)
self.results.extend(parse_sms_file(data)) self.results.extend(parse_sms_file(data))
mms_path = "apps/com.android.providers.telephony/d_f/*_mms_backup" mms_path = "apps/com.android.providers.telephony/d_f/*_mms_backup"
for file in self._get_files_by_pattern(mms_path): for file in self._get_files_by_pattern(mms_path):
self.log.info("Processing MMS backup file at %s", file) self.log.debug("Processing MMS backup file at %s", file)
data = self._get_file_content(file) data = self._get_file_content(file)
self.results.extend(parse_sms_file(data)) self.results.extend(parse_sms_file(data))
+21 -21
View File
@@ -3,31 +3,31 @@
# 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/
from .accessibility import Accessibility from .dumpsys_accessibility import DumpsysAccessibility
from .activities import Activities from .dumpsys_activities import DumpsysActivities
from .appops import Appops from .dumpsys_appops import DumpsysAppops
from .battery_daily import BatteryDaily from .dumpsys_battery_daily import DumpsysBatteryDaily
from .battery_history import BatteryHistory from .dumpsys_battery_history import DumpsysBatteryHistory
from .dbinfo import DBInfo from .dumpsys_dbinfo import DumpsysDBInfo
from .getprop import Getprop from .dumpsys_getprop import DumpsysGetProp
from .packages import Packages from .dumpsys_packages import DumpsysPackages
from .platform_compat import PlatformCompat from .dumpsys_platform_compat import DumpsysPlatformCompat
from .receivers import Receivers from .dumpsys_receivers import DumpsysReceivers
from .adb_state import DumpsysADBState from .dumpsys_adb_state import DumpsysADBState
from .fs_timestamps import BugReportTimestamps from .fs_timestamps import BugReportTimestamps
from .tombstones import Tombstones from .tombstones import Tombstones
BUGREPORT_MODULES = [ BUGREPORT_MODULES = [
Accessibility, DumpsysAccessibility,
Activities, DumpsysActivities,
Appops, DumpsysAppops,
BatteryDaily, DumpsysBatteryDaily,
BatteryHistory, DumpsysBatteryHistory,
DBInfo, DumpsysDBInfo,
Getprop, DumpsysGetProp,
Packages, DumpsysPackages,
PlatformCompat, DumpsysPlatformCompat,
Receivers, DumpsysReceivers,
DumpsysADBState, DumpsysADBState,
BugReportTimestamps, BugReportTimestamps,
Tombstones, Tombstones,
+1 -3
View File
@@ -39,9 +39,7 @@ class BugReportModule(MVTModule):
self.extract_files: List[str] = [] self.extract_files: List[str] = []
self.zip_files: List[str] = [] self.zip_files: List[str] = []
def from_folder( def from_dir(self, extract_path: str, extract_files: List[str]) -> None:
self, extract_path: Optional[str], extract_files: List[str]
) -> None:
self.extract_path = extract_path self.extract_path = extract_path
self.extract_files = extract_files self.extract_files = extract_files
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArti
from .base import BugReportModule from .base import BugReportModule
class Accessibility(DumpsysAccessibilityArtifact, BugReportModule): class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule):
"""This module extracts stats on accessibility.""" """This module extracts stats on accessibility."""
def __init__( def __init__(
@@ -13,7 +13,7 @@ from mvt.android.artifacts.dumpsys_package_activities import (
from .base import BugReportModule from .base import BugReportModule
class Activities(DumpsysPackageActivitiesArtifact, BugReportModule): class DumpsysActivities(DumpsysPackageActivitiesArtifact, BugReportModule):
"""This module extracts details on receivers for risky activities.""" """This module extracts details on receivers for risky activities."""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
from .base import BugReportModule from .base import BugReportModule
class Appops(DumpsysAppopsArtifact, BugReportModule): class DumpsysAppops(DumpsysAppopsArtifact, BugReportModule):
"""This module extracts information on package from App-Ops Manager.""" """This module extracts information on package from App-Ops Manager."""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtif
from .base import BugReportModule from .base import BugReportModule
class BatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule): class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule):
"""This module extracts records from battery daily updates.""" """This module extracts records from battery daily updates."""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryA
from .base import BugReportModule from .base import BugReportModule
class BatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule): class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule):
"""This module extracts records from battery daily updates.""" """This module extracts records from battery daily updates."""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact
from .base import BugReportModule from .base import BugReportModule
class DBInfo(DumpsysDBInfoArtifact, BugReportModule): class DumpsysDBInfo(DumpsysDBInfoArtifact, BugReportModule):
"""This module extracts records from battery daily updates.""" """This module extracts records from battery daily updates."""
slug = "dbinfo" slug = "dbinfo"
@@ -11,7 +11,7 @@ from mvt.android.artifacts.getprop import GetProp as GetPropArtifact
from .base import BugReportModule from .base import BugReportModule
class Getprop(GetPropArtifact, BugReportModule): class DumpsysGetProp(GetPropArtifact, BugReportModule):
"""This module extracts device properties from getprop command.""" """This module extracts device properties from getprop command."""
def __init__( def __init__(
@@ -12,7 +12,7 @@ from mvt.android.utils import DANGEROUS_PERMISSIONS, DANGEROUS_PERMISSIONS_THRES
from .base import BugReportModule from .base import BugReportModule
class Packages(DumpsysPackagesArtifact, BugReportModule): class DumpsysPackages(DumpsysPackagesArtifact, BugReportModule):
"""This module extracts details on receivers for risky activities.""" """This module extracts details on receivers for risky activities."""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatA
from mvt.android.modules.bugreport.base import BugReportModule from mvt.android.modules.bugreport.base import BugReportModule
class PlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule): class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule):
"""This module extracts details on uninstalled apps.""" """This module extracts details on uninstalled apps."""
def __init__( def __init__(
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
from .base import BugReportModule from .base import BugReportModule
class Receivers(DumpsysReceiversArtifact, BugReportModule): class DumpsysReceivers(DumpsysReceiversArtifact, BugReportModule):
"""This module extracts details on receivers for risky activities.""" """This module extracts details on receivers for risky activities."""
def __init__( def __init__(
+4
View File
@@ -22,6 +22,8 @@ class CmdCheckIOCS(Command):
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: Optional[bool] = False,
sub_command: Optional[bool] = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -32,6 +34,8 @@ class CmdCheckIOCS(Command):
module_name=module_name, module_name=module_name,
serial=serial, serial=serial,
module_options=module_options, module_options=module_options,
hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
+14 -3
View File
@@ -27,10 +27,12 @@ class Command:
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: bool = False, hashes: Optional[bool] = False,
sub_command: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__), log: logging.Logger = logging.getLogger(__name__),
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
@@ -44,6 +46,7 @@ class Command:
self.module_name = module_name self.module_name = module_name
self.serial = serial self.serial = serial
self.log = log self.log = log
self.sub_command = sub_command
self.disable_version_check = disable_version_check self.disable_version_check = disable_version_check
self.disable_indicator_check = disable_indicator_check self.disable_indicator_check = disable_indicator_check
@@ -64,8 +67,12 @@ class Command:
# Load IOCs # Load IOCs
self._create_storage() self._create_storage()
self._setup_logging() self._setup_logging()
self.iocs = Indicators(log=log)
self.iocs.load_indicators_files(self.ioc_files) if iocs is not None:
self.iocs = iocs
else:
self.iocs = Indicators(self.log)
self.iocs.load_indicators_files(self.ioc_files)
def _create_storage(self) -> None: def _create_storage(self) -> None:
if self.results_path and not os.path.exists(self.results_path): if self.results_path and not os.path.exists(self.results_path):
@@ -251,6 +258,10 @@ class Command:
except NotImplementedError: except NotImplementedError:
pass pass
# We only store the timeline from the parent/main command
if self.sub_command:
return
self._store_timeline() self._store_timeline()
self._store_info() self._store_info()
+5
View File
@@ -7,6 +7,7 @@ import logging
from typing import Optional from typing import Optional
from mvt.common.command import Command from mvt.common.command import Command
from mvt.common.indicators import Indicators
from .modules.backup import BACKUP_MODULES from .modules.backup import BACKUP_MODULES
from .modules.mixed import MIXED_MODULES from .modules.mixed import MIXED_MODULES
@@ -20,10 +21,12 @@ class CmdIOSCheckBackup(Command):
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: bool = False, hashes: bool = False,
sub_command: bool = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -31,10 +34,12 @@ class CmdIOSCheckBackup(Command):
target_path=target_path, target_path=target_path,
results_path=results_path, results_path=results_path,
ioc_files=ioc_files, ioc_files=ioc_files,
iocs=iocs,
module_name=module_name, module_name=module_name,
serial=serial, serial=serial,
module_options=module_options, module_options=module_options,
hashes=hashes, hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
+5
View File
@@ -7,6 +7,7 @@ import logging
from typing import Optional from typing import Optional
from mvt.common.command import Command from mvt.common.command import Command
from mvt.common.indicators import Indicators
from .modules.fs import FS_MODULES from .modules.fs import FS_MODULES
from .modules.mixed import MIXED_MODULES from .modules.mixed import MIXED_MODULES
@@ -20,10 +21,12 @@ class CmdIOSCheckFS(Command):
target_path: Optional[str] = None, target_path: Optional[str] = None,
results_path: Optional[str] = None, results_path: Optional[str] = None,
ioc_files: Optional[list] = None, ioc_files: Optional[list] = None,
iocs: Optional[Indicators] = None,
module_name: Optional[str] = None, module_name: Optional[str] = None,
serial: Optional[str] = None, serial: Optional[str] = None,
module_options: Optional[dict] = None, module_options: Optional[dict] = None,
hashes: bool = False, hashes: bool = False,
sub_command: bool = False,
disable_version_check: bool = False, disable_version_check: bool = False,
disable_indicator_check: bool = False, disable_indicator_check: bool = False,
) -> None: ) -> None:
@@ -31,9 +34,11 @@ class CmdIOSCheckFS(Command):
target_path=target_path, target_path=target_path,
results_path=results_path, results_path=results_path,
ioc_files=ioc_files, ioc_files=ioc_files,
iocs=iocs,
module_name=module_name, module_name=module_name,
module_options=module_options, module_options=module_options,
hashes=hashes, hashes=hashes,
sub_command=sub_command,
log=log, log=log,
disable_version_check=disable_version_check, disable_version_check=disable_version_check,
disable_indicator_check=disable_indicator_check, disable_indicator_check=disable_indicator_check,
+1 -1
View File
@@ -22,7 +22,7 @@ class TestBackupModule:
for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)): for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)):
for fname in subfiles: for fname in subfiles:
files.append(os.path.relpath(os.path.join(root, fname), backup_path)) files.append(os.path.relpath(os.path.join(root, fname), backup_path))
mod.from_folder(backup_path, files) mod.from_dir(backup_path, files)
run_module(mod) run_module(mod)
assert len(mod.results) == 2 assert len(mod.results) == 2
assert len(mod.results[0]["links"]) == 1 assert len(mod.results[0]["links"]) == 1
@@ -1,27 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_adb import DumpsysADBState
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysADBModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysADBState(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 1
assert len(m.detected) == 0
adb_statedump = m.results[0]
assert "user_keys" in adb_statedump
assert len(adb_statedump["user_keys"]) == 1
@@ -1,24 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_battery_daily import DumpsysBatteryDaily
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysBatteryDailyModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysBatteryDaily(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 3
assert len(m.timeline) == 3
assert len(m.detected) == 0
@@ -1,24 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_battery_history import DumpsysBatteryHistory
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysBatteryHistoryModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysBatteryHistory(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 6
assert len(m.timeline) == 0
assert len(m.detected) == 0
@@ -1,24 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_dbinfo import DumpsysDBInfo
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysDBInfoModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysDBInfo(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 6
assert len(m.timeline) == 0
assert len(m.detected) == 0
@@ -1,23 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_platform_compat import DumpsysPlatformCompat
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysPlatformCompatModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysPlatformCompat(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 2
assert len(m.detected) == 0
@@ -1,23 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_accessibility import DumpsysAccessibility
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysAccessibilityModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysAccessibility(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 4
assert len(m.detected) == 0
@@ -1,29 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_appops import DumpsysAppops
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysAppOpsModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysAppops(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 12
assert len(m.timeline) == 16
detected_by_ioc = [
detected for detected in m.detected if detected.get("matched_indicator")
]
assert len(m.detected) == 1
assert len(detected_by_ioc) == 0
@@ -1,46 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
import logging
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_packages import DumpsysPackages
from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysPackagesModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysPackages(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 2
assert len(m.detected) == 0
assert len(m.timeline) == 6
assert (
m.results[0]["package_name"]
== "com.samsung.android.provider.filterprovider"
)
def test_detection_pkgname(self, indicator_file):
data_path = get_android_androidqf()
m = DumpsysPackages(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate")
m.indicators = ind
run_module(m)
assert len(m.results) == 2
assert len(m.detected) == 1
assert len(m.timeline) == 6
assert m.detected[0]["package_name"] == "com.sec.android.app.DataCreate"
@@ -1,23 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from pathlib import Path
from mvt.android.modules.androidqf.dumpsys_receivers import DumpsysReceivers
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysReceiversModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysReceivers(target_path=data_path)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
assert len(m.results) == 4
assert len(m.detected) == 0
+3 -3
View File
@@ -6,7 +6,7 @@
import logging import logging
from pathlib import Path from pathlib import Path
from mvt.android.modules.androidqf.files import Files from mvt.android.modules.androidqf.aqf_files import AQFFiles
from mvt.common.module import run_module from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files from ..utils import get_android_androidqf, list_files
@@ -15,10 +15,10 @@ from ..utils import get_android_androidqf, list_files
class TestAndroidqfFilesAnalysis: class TestAndroidqfFilesAnalysis:
def test_androidqf_files(self): def test_androidqf_files(self):
data_path = get_android_androidqf() data_path = get_android_androidqf()
m = Files(target_path=data_path, log=logging) m = AQFFiles(target_path=data_path, log=logging)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 3 assert len(m.results) == 3
assert len(m.timeline) == 6 assert len(m.timeline) == 6
+7 -7
View File
@@ -7,7 +7,7 @@ import logging
import zipfile import zipfile
from pathlib import Path from pathlib import Path
from mvt.android.modules.androidqf.getprop import Getprop from mvt.android.modules.androidqf.aqf_getprop import AQFGetProp
from mvt.common.indicators import Indicators from mvt.common.indicators import Indicators
from mvt.common.module import run_module from mvt.common.module import run_module
@@ -17,10 +17,10 @@ from ..utils import get_android_androidqf, get_artifact, list_files
class TestAndroidqfGetpropAnalysis: class TestAndroidqfGetpropAnalysis:
def test_androidqf_getprop(self): def test_androidqf_getprop(self):
data_path = get_android_androidqf() data_path = get_android_androidqf()
m = Getprop(target_path=data_path, log=logging) m = AQFGetProp(target_path=data_path, log=logging)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 10 assert len(m.results) == 10
assert m.results[0]["name"] == "dalvik.vm.appimageformat" assert m.results[0]["name"] == "dalvik.vm.appimageformat"
@@ -30,9 +30,9 @@ class TestAndroidqfGetpropAnalysis:
def test_getprop_parsing_zip(self): def test_getprop_parsing_zip(self):
fpath = get_artifact("androidqf.zip") fpath = get_artifact("androidqf.zip")
m = Getprop(target_path=fpath, log=logging) m = AQFGetProp(target_path=fpath, log=logging)
archive = zipfile.ZipFile(fpath) archive = zipfile.ZipFile(fpath)
m.from_zip_file(archive, archive.namelist()) m.from_zip(archive, archive.namelist())
run_module(m) run_module(m)
assert len(m.results) == 10 assert len(m.results) == 10
assert m.results[0]["name"] == "dalvik.vm.appimageformat" assert m.results[0]["name"] == "dalvik.vm.appimageformat"
@@ -42,10 +42,10 @@ class TestAndroidqfGetpropAnalysis:
def test_androidqf_getprop_detection(self, indicator_file): def test_androidqf_getprop_detection(self, indicator_file):
data_path = get_android_androidqf() data_path = get_android_androidqf()
m = Getprop(target_path=data_path, log=logging) m = AQFGetProp(target_path=data_path, log=logging)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
ind = Indicators(log=logging.getLogger()) ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file) ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree") ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree")
+1 -1
View File
@@ -85,7 +85,7 @@ class TestAndroidqfMountsModule:
m = Mounts(target_path=data_path, log=logging) m = Mounts(target_path=data_path, log=logging)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
+3 -3
View File
@@ -8,7 +8,7 @@ from pathlib import Path
import pytest import pytest
from mvt.android.modules.androidqf.packages import Packages from mvt.android.modules.androidqf.aqf_packages import AQFPackages
from mvt.common.module import run_module from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files from ..utils import get_android_androidqf, list_files
@@ -31,8 +31,8 @@ def file_list(data_path):
@pytest.fixture() @pytest.fixture()
def module(parent_data_path, file_list): def module(parent_data_path, file_list):
m = Packages(target_path=parent_data_path, log=logging) m = AQFPackages(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, file_list) m.from_dir(parent_data_path, file_list)
return m return m
+3 -3
View File
@@ -6,7 +6,7 @@
import logging import logging
from pathlib import Path from pathlib import Path
from mvt.android.modules.androidqf.processes import Processes from mvt.android.modules.androidqf.aqf_processes import AQFProcesses
from mvt.common.module import run_module from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files from ..utils import get_android_androidqf, list_files
@@ -15,10 +15,10 @@ from ..utils import get_android_androidqf, list_files
class TestAndroidqfProcessesAnalysis: class TestAndroidqfProcessesAnalysis:
def test_androidqf_processes(self): def test_androidqf_processes(self):
data_path = get_android_androidqf() data_path = get_android_androidqf()
m = Processes(target_path=data_path, log=logging) m = AQFProcesses(target_path=data_path, log=logging)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 15 assert len(m.results) == 15
assert len(m.timeline) == 0 assert len(m.timeline) == 0
@@ -32,7 +32,7 @@ def file_list(data_path):
@pytest.fixture() @pytest.fixture()
def module(parent_data_path, file_list): def module(parent_data_path, file_list):
m = RootBinaries(target_path=parent_data_path, log=logging) m = RootBinaries(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, file_list) m.from_dir(parent_data_path, file_list)
return m return m
@@ -108,7 +108,7 @@ class TestAndroidqfRootBinaries:
# Test behavior when no root_binaries.json file is present # Test behavior when no root_binaries.json file is present
empty_file_list = [] empty_file_list = []
m = RootBinaries(target_path=parent_data_path, log=logging) m = RootBinaries(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, empty_file_list) m.from_dir(parent_data_path, empty_file_list)
run_module(m) run_module(m)
+3 -3
View File
@@ -5,7 +5,7 @@
from pathlib import Path from pathlib import Path
from mvt.android.modules.androidqf.settings import Settings from mvt.android.modules.androidqf.aqf_settings import AQFSettings
from mvt.common.module import run_module from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files from ..utils import get_android_androidqf, list_files
@@ -14,10 +14,10 @@ from ..utils import get_android_androidqf, list_files
class TestSettingsModule: class TestSettingsModule:
def test_parsing(self): def test_parsing(self):
data_path = get_android_androidqf() data_path = get_android_androidqf()
m = Settings(target_path=data_path) m = AQFSettings(target_path=data_path)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 1 assert len(m.results) == 1
assert "random" in m.results.keys() assert "random" in m.results.keys()
+5 -5
View File
@@ -21,7 +21,7 @@ class TestAndroidqfSMSAnalysis:
m = SMS(target_path=data_path, log=logging) m = SMS(target_path=data_path, log=logging)
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 2 assert len(m.results) == 2
assert len(m.timeline) == 0 assert len(m.timeline) == 0
@@ -36,7 +36,7 @@ class TestAndroidqfSMSAnalysis:
) )
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 1 assert len(m.results) == 1
@@ -52,7 +52,7 @@ class TestAndroidqfSMSAnalysis:
) )
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert prompt_mock.call_count == 1 assert prompt_mock.call_count == 1
assert len(m.results) == 1 assert len(m.results) == 1
@@ -67,7 +67,7 @@ class TestAndroidqfSMSAnalysis:
) )
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 0 assert len(m.results) == 0
assert "Invalid backup password" in caplog.text assert "Invalid backup password" in caplog.text
@@ -82,7 +82,7 @@ class TestAndroidqfSMSAnalysis:
) )
files = list_files(data_path) files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix() parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files) m.from_dir(parent_path, files)
run_module(m) run_module(m)
assert len(m.results) == 0 assert len(m.results) == 0
assert ( assert (
+7 -7
View File
@@ -6,9 +6,9 @@
import os import os
from pathlib import Path from pathlib import Path
from mvt.android.modules.bugreport.appops import Appops from mvt.android.modules.bugreport.dumpsys_appops import DumpsysAppops
from mvt.android.modules.bugreport.getprop import Getprop from mvt.android.modules.bugreport.dumpsys_getprop import DumpsysGetProp
from mvt.android.modules.bugreport.packages import Packages from mvt.android.modules.bugreport.dumpsys_packages import DumpsysPackages
from mvt.android.modules.bugreport.tombstones import Tombstones from mvt.android.modules.bugreport.tombstones import Tombstones
from mvt.common.module import run_module from mvt.common.module import run_module
@@ -26,12 +26,12 @@ class TestBugreportAnalysis:
folder_files.append( folder_files.append(
os.path.relpath(os.path.join(root, file_name), parent_path) os.path.relpath(os.path.join(root, file_name), parent_path)
) )
m.from_folder(fpath, folder_files) m.from_dir(fpath, folder_files)
run_module(m) run_module(m)
return m return m
def test_appops_module(self): def test_appops_module(self):
m = self.launch_bug_report_module(Appops) m = self.launch_bug_report_module(DumpsysAppops)
assert len(m.results) == 12 assert len(m.results) == 12
assert len(m.timeline) == 16 assert len(m.timeline) == 16
@@ -42,7 +42,7 @@ class TestBugreportAnalysis:
assert len(detected_by_ioc) == 0 assert len(detected_by_ioc) == 0
def test_packages_module(self): def test_packages_module(self):
m = self.launch_bug_report_module(Packages) m = self.launch_bug_report_module(DumpsysPackages)
assert len(m.results) == 2 assert len(m.results) == 2
assert ( assert (
m.results[0]["package_name"] m.results[0]["package_name"]
@@ -53,7 +53,7 @@ class TestBugreportAnalysis:
assert len(m.results[1]["permissions"]) == 32 assert len(m.results[1]["permissions"]) == 32
def test_getprop_module(self): def test_getprop_module(self):
m = self.launch_bug_report_module(Getprop) m = self.launch_bug_report_module(DumpsysGetProp)
assert len(m.results) == 0 assert len(m.results) == 0
def test_tombstones_modules(self): def test_tombstones_modules(self):
+2 -1
View File
@@ -32,7 +32,8 @@ class TestCheckAndroidqfCommand:
path = os.path.join(get_artifact_folder(), "androidqf_encrypted") path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
result = runner.invoke(check_androidqf, [path]) result = runner.invoke(check_androidqf, [path])
assert prompt_mock.call_count == 1 # Called twice, once in AnroidQF SMS module and once in Backup SMS module
assert prompt_mock.call_count == 2
assert result.exit_code == 0 assert result.exit_code == 0
def test_check_encrypted_backup_cli(self, mocker): def test_check_encrypted_backup_cli(self, mocker):