mirror of
https://github.com/mvt-project/mvt.git
synced 2024-06-29 07:39:00 +00:00
Adds support for zip files in check-androidqf command
This commit is contained in:
parent
15ce1b7e64
commit
cfd0baaab0
|
@ -9,13 +9,13 @@ import click
|
||||||
|
|
||||||
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
||||||
from mvt.common.help import (
|
from mvt.common.help import (
|
||||||
|
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
||||||
HELP_MSG_FAST,
|
HELP_MSG_FAST,
|
||||||
HELP_MSG_HASHES,
|
HELP_MSG_HASHES,
|
||||||
HELP_MSG_IOC,
|
HELP_MSG_IOC,
|
||||||
HELP_MSG_LIST_MODULES,
|
HELP_MSG_LIST_MODULES,
|
||||||
HELP_MSG_MODULE,
|
HELP_MSG_MODULE,
|
||||||
HELP_MSG_NONINTERACTIVE,
|
HELP_MSG_NONINTERACTIVE,
|
||||||
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
|
||||||
HELP_MSG_OUTPUT,
|
HELP_MSG_OUTPUT,
|
||||||
HELP_MSG_SERIAL,
|
HELP_MSG_SERIAL,
|
||||||
HELP_MSG_VERBOSE,
|
HELP_MSG_VERBOSE,
|
||||||
|
@ -32,8 +32,8 @@ from .cmd_download_apks import DownloadAPKs
|
||||||
from .modules.adb import ADB_MODULES
|
from .modules.adb import ADB_MODULES
|
||||||
from .modules.adb.packages import Packages
|
from .modules.adb.packages import Packages
|
||||||
from .modules.backup import BACKUP_MODULES
|
from .modules.backup import BACKUP_MODULES
|
||||||
from .modules.bugreport import BUGREPORT_MODULES
|
|
||||||
from .modules.backup.helpers import cli_load_android_backup_password
|
from .modules.backup.helpers import cli_load_android_backup_password
|
||||||
|
from .modules.bugreport import BUGREPORT_MODULES
|
||||||
|
|
||||||
init_logging()
|
init_logging()
|
||||||
log = logging.getLogger("mvt")
|
log = logging.getLogger("mvt")
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
import os
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
|
|
||||||
|
@ -37,3 +40,28 @@ 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.archive: Optional[zipfile.ZipFile] = None
|
||||||
|
self.files: List[str] = []
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if os.path.isdir(self.target_path):
|
||||||
|
self.format = "dir"
|
||||||
|
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
||||||
|
target_abs_path = os.path.abspath(self.target_path)
|
||||||
|
for root, subdirs, subfiles in os.walk(target_abs_path):
|
||||||
|
for fname in subfiles:
|
||||||
|
file_path = os.path.relpath(os.path.join(root, fname), parent_path)
|
||||||
|
self.files.append(file_path)
|
||||||
|
elif os.path.isfile(self.target_path):
|
||||||
|
self.format = "zip"
|
||||||
|
self.archive = zipfile.ZipFile(self.target_path)
|
||||||
|
self.files = self.archive.namelist()
|
||||||
|
|
||||||
|
def module_init(self, module):
|
||||||
|
if self.format == "zip":
|
||||||
|
module.from_zip_file(self.archive, self.files)
|
||||||
|
else:
|
||||||
|
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
||||||
|
module.from_folder(parent_path, self.files)
|
||||||
|
|
|
@ -12,13 +12,13 @@ from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from mvt.android.modules.backup.base import BackupExtraction
|
from mvt.android.modules.backup.base import BackupExtraction
|
||||||
|
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||||
from mvt.android.parsers.backup import (
|
from mvt.android.parsers.backup import (
|
||||||
AndroidBackupParsingError,
|
AndroidBackupParsingError,
|
||||||
InvalidBackupPassword,
|
InvalidBackupPassword,
|
||||||
parse_ab_header,
|
parse_ab_header,
|
||||||
parse_backup_file,
|
parse_backup_file,
|
||||||
)
|
)
|
||||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
|
|
||||||
from .modules.backup import BACKUP_MODULES
|
from .modules.backup import BACKUP_MODULES
|
||||||
|
|
|
@ -24,12 +24,12 @@ from adb_shell.exceptions import (
|
||||||
)
|
)
|
||||||
from usb1 import USBErrorAccess, USBErrorBusy
|
from usb1 import USBErrorAccess, USBErrorBusy
|
||||||
|
|
||||||
|
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||||
from mvt.android.parsers.backup import (
|
from mvt.android.parsers.backup import (
|
||||||
InvalidBackupPassword,
|
InvalidBackupPassword,
|
||||||
parse_ab_header,
|
parse_ab_header,
|
||||||
parse_backup_file,
|
parse_backup_file,
|
||||||
)
|
)
|
||||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
|
||||||
from mvt.common.module import InsufficientPrivileges, MVTModule
|
from mvt.common.module import InsufficientPrivileges, MVTModule
|
||||||
|
|
||||||
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
|
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import zipfile
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from mvt.common.module import MVTModule
|
from mvt.common.module import MVTModule
|
||||||
|
@ -31,13 +32,28 @@ class AndroidQFModule(MVTModule):
|
||||||
log=log,
|
log=log,
|
||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
self._path: str = target_path
|
||||||
|
self.files: List[str] = []
|
||||||
|
self.archive: Optional[zipfile.ZipFile] = None
|
||||||
|
|
||||||
self._path = target_path
|
def from_folder(self, parent_path: str, files: List[str]):
|
||||||
self._files = []
|
self.parent_path = parent_path
|
||||||
|
self.files = files
|
||||||
|
|
||||||
for root, dirs, files in os.walk(target_path):
|
def from_zip_file(self, archive: zipfile.ZipFile, files: List[str]):
|
||||||
for name in files:
|
self.archive = archive
|
||||||
self._files.append(os.path.join(root, name))
|
self.files = files
|
||||||
|
|
||||||
def _get_files_by_pattern(self, pattern):
|
def _get_files_by_pattern(self, pattern: str):
|
||||||
return fnmatch.filter(self._files, pattern)
|
return fnmatch.filter(self.files, pattern)
|
||||||
|
|
||||||
|
def _get_file_content(self, file_path):
|
||||||
|
if self.archive:
|
||||||
|
handle = self.archive.open(file_path)
|
||||||
|
else:
|
||||||
|
handle = open(os.path.join(self.parent_path, file_path), "rb")
|
||||||
|
|
||||||
|
data = handle.read()
|
||||||
|
handle.close()
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
|
@ -49,21 +49,21 @@ class DumpsysAccessibility(AndroidQFModule):
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
in_accessibility = False
|
in_accessibility = False
|
||||||
with open(dumpsys_file[0]) as handle:
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
for line in handle:
|
for line in data.decode("utf-8").split("\n"):
|
||||||
if line.strip().startswith("DUMP OF SERVICE accessibility:"):
|
if line.strip().startswith("DUMP OF SERVICE accessibility:"):
|
||||||
in_accessibility = True
|
in_accessibility = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not in_accessibility:
|
if not in_accessibility:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.strip().startswith(
|
if line.strip().startswith(
|
||||||
"-------------------------------------------------------------------------------"
|
"-------------------------------------------------------------------------------"
|
||||||
): # pylint: disable=line-too-long
|
): # pylint: disable=line-too-long
|
||||||
break
|
break
|
||||||
|
|
||||||
lines.append(line.rstrip())
|
lines.append(line.rstrip())
|
||||||
|
|
||||||
self.results = parse_dumpsys_accessibility("\n".join(lines))
|
self.results = parse_dumpsys_accessibility("\n".join(lines))
|
||||||
|
|
||||||
|
|
|
@ -52,21 +52,21 @@ class DumpsysActivities(AndroidQFModule):
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
in_package = False
|
in_package = False
|
||||||
with open(dumpsys_file[0]) as handle:
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
for line in handle:
|
for line in data.decode("utf-8").split("\n"):
|
||||||
if line.strip() == "DUMP OF SERVICE package:":
|
if line.strip() == "DUMP OF SERVICE package:":
|
||||||
in_package = True
|
in_package = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not in_package:
|
if not in_package:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.strip().startswith(
|
if line.strip().startswith(
|
||||||
"------------------------------------------------------------------------------"
|
"------------------------------------------------------------------------------"
|
||||||
): # pylint: disable=line-too-long
|
): # pylint: disable=line-too-long
|
||||||
break
|
break
|
||||||
|
|
||||||
lines.append(line.rstrip())
|
lines.append(line.rstrip())
|
||||||
|
|
||||||
self.results = parse_dumpsys_activity_resolver_table("\n".join(lines))
|
self.results = parse_dumpsys_activity_resolver_table("\n".join(lines))
|
||||||
|
|
||||||
|
|
|
@ -76,19 +76,19 @@ class DumpsysAppops(AndroidQFModule):
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
in_package = False
|
in_package = False
|
||||||
with open(dumpsys_file[0]) as handle:
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
for line in handle:
|
for line in data.decode("utf-8").split("\n"):
|
||||||
if line.startswith("DUMP OF SERVICE appops:"):
|
if line.startswith("DUMP OF SERVICE appops:"):
|
||||||
in_package = True
|
in_package = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if in_package:
|
if in_package:
|
||||||
if line.startswith(
|
if line.startswith(
|
||||||
"-------------------------------------------------------------------------------"
|
"-------------------------------------------------------------------------------"
|
||||||
): # pylint: disable=line-too-long
|
): # pylint: disable=line-too-long
|
||||||
break
|
break
|
||||||
|
|
||||||
lines.append(line.rstrip())
|
lines.append(line.rstrip())
|
||||||
|
|
||||||
self.results = parse_dumpsys_appops("\n".join(lines))
|
self.results = parse_dumpsys_appops("\n".join(lines))
|
||||||
self.log.info("Identified %d applications in AppOps Manager", len(self.results))
|
self.log.info("Identified %d applications in AppOps Manager", len(self.results))
|
||||||
|
|
|
@ -78,13 +78,12 @@ class DumpsysPackages(AndroidQFModule):
|
||||||
self.log.info("Dumpsys file not found")
|
self.log.info("Dumpsys file not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(dumpsys_file[0]) as handle:
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
data = handle.read().split("\n")
|
|
||||||
|
|
||||||
package = []
|
package = []
|
||||||
in_service = False
|
in_service = False
|
||||||
in_package_list = False
|
in_package_list = False
|
||||||
for line in data:
|
for line in data.decode("utf-8").split("\n"):
|
||||||
if line.strip().startswith("DUMP OF SERVICE package:"):
|
if line.strip().startswith("DUMP OF SERVICE package:"):
|
||||||
in_service = True
|
in_service = True
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -86,21 +86,21 @@ class DumpsysReceivers(AndroidQFModule):
|
||||||
|
|
||||||
in_receivers = False
|
in_receivers = False
|
||||||
lines = []
|
lines = []
|
||||||
with open(dumpsys_file[0]) as handle:
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
for line in handle:
|
for line in data.decode("utf-8").split("\n"):
|
||||||
if line.strip() == "DUMP OF SERVICE package:":
|
if line.strip() == "DUMP OF SERVICE package:":
|
||||||
in_receivers = True
|
in_receivers = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not in_receivers:
|
if not in_receivers:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.strip().startswith(
|
if line.strip().startswith(
|
||||||
"------------------------------------------------------------------------------"
|
"------------------------------------------------------------------------------"
|
||||||
): # pylint: disable=line-too-long
|
): # pylint: disable=line-too-long
|
||||||
break
|
break
|
||||||
|
|
||||||
lines.append(line.rstrip())
|
lines.append(line.rstrip())
|
||||||
|
|
||||||
self.results = parse_dumpsys_receiver_resolver_table("\n".join(lines))
|
self.results = parse_dumpsys_receiver_resolver_table("\n".join(lines))
|
||||||
|
|
||||||
|
|
|
@ -64,8 +64,7 @@ class Getprop(AndroidQFModule):
|
||||||
self.log.info("getprop.txt file not found")
|
self.log.info("getprop.txt file not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(getprop_files[0]) as f:
|
data = self._get_file_content(getprop_files[0]).decode("utf-8")
|
||||||
data = f.read()
|
|
||||||
|
|
||||||
self.results = parse_getprop(data)
|
self.results = parse_getprop(data)
|
||||||
for entry in self.results:
|
for entry in self.results:
|
||||||
|
|
|
@ -93,7 +93,5 @@ class Processes(AndroidQFModule):
|
||||||
if not ps_files:
|
if not ps_files:
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(ps_files[0]) as handle:
|
self._parse_ps(self._get_file_content(ps_files[0]).decode("utf-8"))
|
||||||
self._parse_ps(handle.read())
|
|
||||||
|
|
||||||
self.log.info("Identified %d running processes", len(self.results))
|
self.log.info("Identified %d running processes", len(self.results))
|
||||||
|
|
|
@ -38,29 +38,28 @@ class Settings(AndroidQFModule):
|
||||||
namespace = setting_file[setting_file.rfind("_") + 1 : -4]
|
namespace = setting_file[setting_file.rfind("_") + 1 : -4]
|
||||||
|
|
||||||
self.results[namespace] = {}
|
self.results[namespace] = {}
|
||||||
|
data = self._get_file_content(setting_file)
|
||||||
|
for line in data.decode("utf-8").split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
try:
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
with open(setting_file) as handle:
|
try:
|
||||||
for line in handle:
|
self.results[namespace][key] = value
|
||||||
line = line.strip()
|
except IndexError:
|
||||||
try:
|
continue
|
||||||
key, value = line.split("=", 1)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
for danger in ANDROID_DANGEROUS_SETTINGS:
|
||||||
self.results[namespace][key] = value
|
if danger["key"] == key and danger["safe_value"] != value:
|
||||||
except IndexError:
|
self.log.warning(
|
||||||
continue
|
'Found suspicious setting "%s = %s" (%s)',
|
||||||
|
key,
|
||||||
for danger in ANDROID_DANGEROUS_SETTINGS:
|
value,
|
||||||
if danger["key"] == key and danger["safe_value"] != value:
|
danger["description"],
|
||||||
self.log.warning(
|
)
|
||||||
'Found suspicious setting "%s = %s" (%s)',
|
break
|
||||||
key,
|
|
||||||
value,
|
|
||||||
danger["description"],
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"Identified %d settings", sum([len(val) for val in self.results.values()])
|
"Identified %d settings", sum([len(val) for val in self.results.values()])
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||||
from mvt.android.parsers.backup import (
|
from mvt.android.parsers.backup import (
|
||||||
AndroidBackupParsingError,
|
AndroidBackupParsingError,
|
||||||
InvalidBackupPassword,
|
InvalidBackupPassword,
|
||||||
|
@ -13,7 +14,6 @@ from mvt.android.parsers.backup import (
|
||||||
parse_backup_file,
|
parse_backup_file,
|
||||||
parse_tar_for_sms,
|
parse_tar_for_sms,
|
||||||
)
|
)
|
||||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
|
||||||
|
|
||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
@ -96,8 +96,5 @@ class SMS(AndroidQFModule):
|
||||||
self.log.info("No backup data found")
|
self.log.info("No backup data found")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(files[0], "rb") as handle:
|
self.parse_backup(self._get_file_content(files[0]))
|
||||||
data = handle.read()
|
|
||||||
|
|
||||||
self.parse_backup(data)
|
|
||||||
self.log.info("Identified %d SMS in backup data", len(self.results))
|
self.log.info("Identified %d SMS in backup data", len(self.results))
|
||||||
|
|
|
@ -7,7 +7,6 @@ import os
|
||||||
|
|
||||||
from rich.prompt import Prompt
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
|
||||||
MVT_ANDROID_BACKUP_PASSWORD = "MVT_ANDROID_BACKUP_PASSWORD"
|
MVT_ANDROID_BACKUP_PASSWORD = "MVT_ANDROID_BACKUP_PASSWORD"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from typing import Union, Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from mvt.common.utils import convert_mactime_to_iso
|
from mvt.common.utils import convert_mactime_to_iso
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,21 @@
|
||||||
# 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 pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.dumpsys_accessibility import DumpsysAccessibility
|
from mvt.android.modules.androidqf.dumpsys_accessibility import DumpsysAccessibility
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
class TestDumpsysAccessibilityModule:
|
class TestDumpsysAccessibilityModule:
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = DumpsysAccessibility(target_path=data_path)
|
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)
|
run_module(m)
|
||||||
assert len(m.results) == 4
|
assert len(m.results) == 4
|
||||||
assert len(m.detected) == 0
|
assert len(m.detected) == 0
|
||||||
|
|
|
@ -3,16 +3,21 @@
|
||||||
# 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 pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.dumpsys_appops import DumpsysAppops
|
from mvt.android.modules.androidqf.dumpsys_appops import DumpsysAppops
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
class TestDumpsysAppOpsModule:
|
class TestDumpsysAppOpsModule:
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = DumpsysAppops(target_path=data_path)
|
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)
|
run_module(m)
|
||||||
assert len(m.results) == 12
|
assert len(m.results) == 12
|
||||||
assert len(m.timeline) == 16
|
assert len(m.timeline) == 16
|
||||||
|
|
|
@ -4,18 +4,22 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.dumpsys_packages import DumpsysPackages
|
from mvt.android.modules.androidqf.dumpsys_packages import DumpsysPackages
|
||||||
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
|
||||||
|
|
||||||
from ..utils import get_android_androidqf
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
class TestDumpsysPackagesModule:
|
class TestDumpsysPackagesModule:
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = DumpsysPackages(target_path=data_path)
|
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)
|
run_module(m)
|
||||||
assert len(m.results) == 2
|
assert len(m.results) == 2
|
||||||
assert len(m.detected) == 0
|
assert len(m.detected) == 0
|
||||||
|
@ -28,6 +32,9 @@ class TestDumpsysPackagesModule:
|
||||||
def test_detection_pkgname(self, indicator_file):
|
def test_detection_pkgname(self, indicator_file):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = DumpsysPackages(target_path=data_path)
|
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 = Indicators(log=logging.getLogger())
|
||||||
ind.parse_stix2(indicator_file)
|
ind.parse_stix2(indicator_file)
|
||||||
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate")
|
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate")
|
||||||
|
|
|
@ -3,16 +3,21 @@
|
||||||
# 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 pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.dumpsys_receivers import DumpsysReceivers
|
from mvt.android.modules.androidqf.dumpsys_receivers import DumpsysReceivers
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
class TestDumpsysReceiversModule:
|
class TestDumpsysReceiversModule:
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = DumpsysReceivers(target_path=data_path)
|
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)
|
run_module(m)
|
||||||
assert len(m.results) == 4
|
assert len(m.results) == 4
|
||||||
assert len(m.detected) == 0
|
assert len(m.detected) == 0
|
||||||
|
|
|
@ -4,20 +4,35 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.getprop import Getprop
|
from mvt.android.modules.androidqf.getprop import Getprop
|
||||||
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
|
||||||
|
|
||||||
from ..utils import get_artifact_folder
|
from ..utils import get_android_androidqf, get_artifact, list_files
|
||||||
|
|
||||||
|
|
||||||
class TestAndroidqfGetpropAnalysis:
|
class TestAndroidqfGetpropAnalysis:
|
||||||
def test_androidqf_getprop(self):
|
def test_androidqf_getprop(self):
|
||||||
m = Getprop(
|
data_path = get_android_androidqf()
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
m = Getprop(target_path=data_path, log=logging)
|
||||||
)
|
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) == 10
|
||||||
|
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||||
|
assert m.results[0]["value"] == "lz4"
|
||||||
|
assert len(m.timeline) == 0
|
||||||
|
assert len(m.detected) == 0
|
||||||
|
|
||||||
|
def test_getprop_parsing_zip(self):
|
||||||
|
fpath = get_artifact("androidqf.zip")
|
||||||
|
m = Getprop(target_path=fpath, log=logging)
|
||||||
|
archive = zipfile.ZipFile(fpath)
|
||||||
|
m.from_zip_file(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"
|
||||||
|
@ -26,9 +41,11 @@ class TestAndroidqfGetpropAnalysis:
|
||||||
assert len(m.detected) == 0
|
assert len(m.detected) == 0
|
||||||
|
|
||||||
def test_androidqf_getprop_detection(self, indicator_file):
|
def test_androidqf_getprop_detection(self, indicator_file):
|
||||||
m = Getprop(
|
data_path = get_android_androidqf()
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
m = Getprop(target_path=data_path, log=logging)
|
||||||
)
|
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 = 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")
|
||||||
|
|
|
@ -4,19 +4,21 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.processes import Processes
|
from mvt.android.modules.androidqf.processes import Processes
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_artifact_folder
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
class TestAndroidqfProcessesAnalysis:
|
class TestAndroidqfProcessesAnalysis:
|
||||||
def test_androidqf_processes(self):
|
def test_androidqf_processes(self):
|
||||||
m = Processes(
|
data_path = get_android_androidqf()
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
m = Processes(target_path=data_path, log=logging)
|
||||||
)
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(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
|
||||||
|
|
|
@ -3,16 +3,21 @@
|
||||||
# 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 pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.settings import Settings
|
from mvt.android.modules.androidqf.settings import Settings
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf
|
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 = Settings(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)
|
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,65 +5,84 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.sms import SMS
|
from mvt.android.modules.androidqf.sms import SMS
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_artifact_folder
|
from ..utils import get_android_androidqf, get_artifact_folder, list_files
|
||||||
|
|
||||||
TEST_BACKUP_PASSWORD = "123456"
|
TEST_BACKUP_PASSWORD = "123456"
|
||||||
|
|
||||||
|
|
||||||
class TestAndroidqfSMSAnalysis:
|
class TestAndroidqfSMSAnalysis:
|
||||||
def test_androidqf_sms(self):
|
def test_androidqf_sms(self):
|
||||||
m = SMS(
|
data_path = get_android_androidqf()
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
m = SMS(target_path=data_path, log=logging)
|
||||||
)
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(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
|
||||||
assert len(m.detected) == 0
|
assert len(m.detected) == 0
|
||||||
|
|
||||||
def test_androidqf_sms_encrypted_password_valid(self):
|
def test_androidqf_sms_encrypted_password_valid(self):
|
||||||
|
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||||
m = SMS(
|
m = SMS(
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
target_path=data_path,
|
||||||
log=logging,
|
log=logging,
|
||||||
module_options={"backup_password": TEST_BACKUP_PASSWORD},
|
module_options={"backup_password": TEST_BACKUP_PASSWORD},
|
||||||
)
|
)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 1
|
assert len(m.results) == 1
|
||||||
|
|
||||||
def test_androidqf_sms_encrypted_password_prompt(self, mocker):
|
def test_androidqf_sms_encrypted_password_prompt(self, mocker):
|
||||||
|
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||||
prompt_mock = mocker.patch(
|
prompt_mock = mocker.patch(
|
||||||
"rich.prompt.Prompt.ask", return_value=TEST_BACKUP_PASSWORD
|
"rich.prompt.Prompt.ask", return_value=TEST_BACKUP_PASSWORD
|
||||||
)
|
)
|
||||||
m = SMS(
|
m = SMS(
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
target_path=data_path,
|
||||||
log=logging,
|
log=logging,
|
||||||
module_options={},
|
module_options={},
|
||||||
)
|
)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(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
|
||||||
|
|
||||||
def test_androidqf_sms_encrypted_password_invalid(self, caplog):
|
def test_androidqf_sms_encrypted_password_invalid(self, caplog):
|
||||||
|
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||||
with caplog.at_level(logging.CRITICAL):
|
with caplog.at_level(logging.CRITICAL):
|
||||||
m = SMS(
|
m = SMS(
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
target_path=data_path,
|
||||||
log=logging,
|
log=logging,
|
||||||
module_options={"backup_password": "invalid_password"},
|
module_options={"backup_password": "invalid_password"},
|
||||||
)
|
)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(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
|
||||||
|
|
||||||
def test_androidqf_sms_encrypted_no_interactive(self, caplog):
|
def test_androidqf_sms_encrypted_no_interactive(self, caplog):
|
||||||
|
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||||
with caplog.at_level(logging.CRITICAL):
|
with caplog.at_level(logging.CRITICAL):
|
||||||
m = SMS(
|
m = SMS(
|
||||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
target_path=data_path,
|
||||||
log=logging,
|
log=logging,
|
||||||
module_options={"interactive": False},
|
module_options={"interactive": False},
|
||||||
)
|
)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 0
|
assert len(m.results) == 0
|
||||||
assert (
|
assert (
|
||||||
|
|
BIN
tests/artifacts/androidqf.zip
Normal file
BIN
tests/artifacts/androidqf.zip
Normal file
Binary file not shown.
|
@ -11,7 +11,6 @@ from mvt.android.cli import check_androidqf
|
||||||
|
|
||||||
from .utils import get_artifact_folder
|
from .utils import get_artifact_folder
|
||||||
|
|
||||||
|
|
||||||
TEST_BACKUP_PASSWORD = "123456"
|
TEST_BACKUP_PASSWORD = "123456"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
# 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/
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ from mvt.android.cli import check_backup
|
||||||
|
|
||||||
from .utils import get_artifact_folder
|
from .utils import get_artifact_folder
|
||||||
|
|
||||||
|
|
||||||
TEST_BACKUP_PASSWORD = "123456"
|
TEST_BACKUP_PASSWORD = "123456"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def get_artifact(fname):
|
def get_artifact(fname):
|
||||||
|
@ -34,3 +35,15 @@ def get_android_androidqf():
|
||||||
|
|
||||||
def get_indicator_file():
|
def get_indicator_file():
|
||||||
print("PYTEST env", os.getenv("PYTEST_CURRENT_TEST"))
|
print("PYTEST env", os.getenv("PYTEST_CURRENT_TEST"))
|
||||||
|
|
||||||
|
|
||||||
|
def list_files(path: str):
|
||||||
|
files = []
|
||||||
|
parent_path = Path(path).absolute().parent.as_posix()
|
||||||
|
target_abs_path = os.path.abspath(path)
|
||||||
|
for root, subdirs, subfiles in os.walk(target_abs_path):
|
||||||
|
for fname in subfiles:
|
||||||
|
file_path = os.path.relpath(os.path.join(root, fname), parent_path)
|
||||||
|
files.append(file_path)
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
Loading…
Reference in New Issue
Block a user