mirror of
https://github.com/mvt-project/mvt.git
synced 2024-06-18 10:29:01 +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.help import (
|
||||
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
||||
HELP_MSG_FAST,
|
||||
HELP_MSG_HASHES,
|
||||
HELP_MSG_IOC,
|
||||
HELP_MSG_LIST_MODULES,
|
||||
HELP_MSG_MODULE,
|
||||
HELP_MSG_NONINTERACTIVE,
|
||||
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
||||
HELP_MSG_OUTPUT,
|
||||
HELP_MSG_SERIAL,
|
||||
HELP_MSG_VERBOSE,
|
||||
|
@ -32,8 +32,8 @@ from .cmd_download_apks import DownloadAPKs
|
|||
from .modules.adb import ADB_MODULES
|
||||
from .modules.adb.packages import Packages
|
||||
from .modules.backup import BACKUP_MODULES
|
||||
from .modules.bugreport import BUGREPORT_MODULES
|
||||
from .modules.backup.helpers import cli_load_android_backup_password
|
||||
from .modules.bugreport import BUGREPORT_MODULES
|
||||
|
||||
init_logging()
|
||||
log = logging.getLogger("mvt")
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
# https://license.mvt.re/1.1/
|
||||
|
||||
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
|
||||
|
||||
|
@ -37,3 +40,28 @@ class CmdAndroidCheckAndroidQF(Command):
|
|||
|
||||
self.name = "check-androidqf"
|
||||
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 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 (
|
||||
AndroidBackupParsingError,
|
||||
InvalidBackupPassword,
|
||||
parse_ab_header,
|
||||
parse_backup_file,
|
||||
)
|
||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||
from mvt.common.command import Command
|
||||
|
||||
from .modules.backup import BACKUP_MODULES
|
||||
|
|
|
@ -24,12 +24,12 @@ from adb_shell.exceptions import (
|
|||
)
|
||||
from usb1 import USBErrorAccess, USBErrorBusy
|
||||
|
||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||
from mvt.android.parsers.backup import (
|
||||
InvalidBackupPassword,
|
||||
parse_ab_header,
|
||||
parse_backup_file,
|
||||
)
|
||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||
from mvt.common.module import InsufficientPrivileges, MVTModule
|
||||
|
||||
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
import zipfile
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from mvt.common.module import MVTModule
|
||||
|
@ -31,13 +32,28 @@ class AndroidQFModule(MVTModule):
|
|||
log=log,
|
||||
results=results,
|
||||
)
|
||||
self._path: str = target_path
|
||||
self.files: List[str] = []
|
||||
self.archive: Optional[zipfile.ZipFile] = None
|
||||
|
||||
self._path = target_path
|
||||
self._files = []
|
||||
def from_folder(self, parent_path: str, files: List[str]):
|
||||
self.parent_path = parent_path
|
||||
self.files = files
|
||||
|
||||
for root, dirs, files in os.walk(target_path):
|
||||
for name in files:
|
||||
self._files.append(os.path.join(root, name))
|
||||
def from_zip_file(self, archive: zipfile.ZipFile, files: List[str]):
|
||||
self.archive = archive
|
||||
self.files = files
|
||||
|
||||
def _get_files_by_pattern(self, pattern):
|
||||
return fnmatch.filter(self._files, pattern)
|
||||
def _get_files_by_pattern(self, pattern: str):
|
||||
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 = []
|
||||
in_accessibility = False
|
||||
with open(dumpsys_file[0]) as handle:
|
||||
for line in handle:
|
||||
if line.strip().startswith("DUMP OF SERVICE accessibility:"):
|
||||
in_accessibility = True
|
||||
continue
|
||||
data = self._get_file_content(dumpsys_file[0])
|
||||
for line in data.decode("utf-8").split("\n"):
|
||||
if line.strip().startswith("DUMP OF SERVICE accessibility:"):
|
||||
in_accessibility = True
|
||||
continue
|
||||
|
||||
if not in_accessibility:
|
||||
continue
|
||||
if not in_accessibility:
|
||||
continue
|
||||
|
||||
if line.strip().startswith(
|
||||
"-------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
if line.strip().startswith(
|
||||
"-------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
|
||||
lines.append(line.rstrip())
|
||||
lines.append(line.rstrip())
|
||||
|
||||
self.results = parse_dumpsys_accessibility("\n".join(lines))
|
||||
|
||||
|
|
|
@ -52,21 +52,21 @@ class DumpsysActivities(AndroidQFModule):
|
|||
|
||||
lines = []
|
||||
in_package = False
|
||||
with open(dumpsys_file[0]) as handle:
|
||||
for line in handle:
|
||||
if line.strip() == "DUMP OF SERVICE package:":
|
||||
in_package = True
|
||||
continue
|
||||
data = self._get_file_content(dumpsys_file[0])
|
||||
for line in data.decode("utf-8").split("\n"):
|
||||
if line.strip() == "DUMP OF SERVICE package:":
|
||||
in_package = True
|
||||
continue
|
||||
|
||||
if not in_package:
|
||||
continue
|
||||
if not in_package:
|
||||
continue
|
||||
|
||||
if line.strip().startswith(
|
||||
"------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
if line.strip().startswith(
|
||||
"------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
|
||||
lines.append(line.rstrip())
|
||||
lines.append(line.rstrip())
|
||||
|
||||
self.results = parse_dumpsys_activity_resolver_table("\n".join(lines))
|
||||
|
||||
|
|
|
@ -76,19 +76,19 @@ class DumpsysAppops(AndroidQFModule):
|
|||
|
||||
lines = []
|
||||
in_package = False
|
||||
with open(dumpsys_file[0]) as handle:
|
||||
for line in handle:
|
||||
if line.startswith("DUMP OF SERVICE appops:"):
|
||||
in_package = True
|
||||
continue
|
||||
data = self._get_file_content(dumpsys_file[0])
|
||||
for line in data.decode("utf-8").split("\n"):
|
||||
if line.startswith("DUMP OF SERVICE appops:"):
|
||||
in_package = True
|
||||
continue
|
||||
|
||||
if in_package:
|
||||
if line.startswith(
|
||||
"-------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
if in_package:
|
||||
if line.startswith(
|
||||
"-------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
|
||||
lines.append(line.rstrip())
|
||||
lines.append(line.rstrip())
|
||||
|
||||
self.results = parse_dumpsys_appops("\n".join(lines))
|
||||
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")
|
||||
return
|
||||
|
||||
with open(dumpsys_file[0]) as handle:
|
||||
data = handle.read().split("\n")
|
||||
data = self._get_file_content(dumpsys_file[0])
|
||||
|
||||
package = []
|
||||
in_service = 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:"):
|
||||
in_service = True
|
||||
continue
|
||||
|
|
|
@ -86,21 +86,21 @@ class DumpsysReceivers(AndroidQFModule):
|
|||
|
||||
in_receivers = False
|
||||
lines = []
|
||||
with open(dumpsys_file[0]) as handle:
|
||||
for line in handle:
|
||||
if line.strip() == "DUMP OF SERVICE package:":
|
||||
in_receivers = True
|
||||
continue
|
||||
data = self._get_file_content(dumpsys_file[0])
|
||||
for line in data.decode("utf-8").split("\n"):
|
||||
if line.strip() == "DUMP OF SERVICE package:":
|
||||
in_receivers = True
|
||||
continue
|
||||
|
||||
if not in_receivers:
|
||||
continue
|
||||
if not in_receivers:
|
||||
continue
|
||||
|
||||
if line.strip().startswith(
|
||||
"------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
if line.strip().startswith(
|
||||
"------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
|
||||
lines.append(line.rstrip())
|
||||
lines.append(line.rstrip())
|
||||
|
||||
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")
|
||||
return
|
||||
|
||||
with open(getprop_files[0]) as f:
|
||||
data = f.read()
|
||||
data = self._get_file_content(getprop_files[0]).decode("utf-8")
|
||||
|
||||
self.results = parse_getprop(data)
|
||||
for entry in self.results:
|
||||
|
|
|
@ -93,7 +93,5 @@ class Processes(AndroidQFModule):
|
|||
if not ps_files:
|
||||
return
|
||||
|
||||
with open(ps_files[0]) as handle:
|
||||
self._parse_ps(handle.read())
|
||||
|
||||
self._parse_ps(self._get_file_content(ps_files[0]).decode("utf-8"))
|
||||
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]
|
||||
|
||||
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:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
try:
|
||||
key, value = line.split("=", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
try:
|
||||
self.results[namespace][key] = value
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
try:
|
||||
self.results[namespace][key] = value
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
for danger in ANDROID_DANGEROUS_SETTINGS:
|
||||
if danger["key"] == key and danger["safe_value"] != value:
|
||||
self.log.warning(
|
||||
'Found suspicious setting "%s = %s" (%s)',
|
||||
key,
|
||||
value,
|
||||
danger["description"],
|
||||
)
|
||||
break
|
||||
for danger in ANDROID_DANGEROUS_SETTINGS:
|
||||
if danger["key"] == key and danger["safe_value"] != value:
|
||||
self.log.warning(
|
||||
'Found suspicious setting "%s = %s" (%s)',
|
||||
key,
|
||||
value,
|
||||
danger["description"],
|
||||
)
|
||||
break
|
||||
|
||||
self.log.info(
|
||||
"Identified %d settings", sum([len(val) for val in self.results.values()])
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||
from mvt.android.parsers.backup import (
|
||||
AndroidBackupParsingError,
|
||||
InvalidBackupPassword,
|
||||
|
@ -13,7 +14,6 @@ from mvt.android.parsers.backup import (
|
|||
parse_backup_file,
|
||||
parse_tar_for_sms,
|
||||
)
|
||||
from mvt.android.modules.backup.helpers import prompt_or_load_android_backup_password
|
||||
|
||||
from .base import AndroidQFModule
|
||||
|
||||
|
@ -96,8 +96,5 @@ class SMS(AndroidQFModule):
|
|||
self.log.info("No backup data found")
|
||||
return
|
||||
|
||||
with open(files[0], "rb") as handle:
|
||||
data = handle.read()
|
||||
|
||||
self.parse_backup(data)
|
||||
self.parse_backup(self._get_file_content(files[0]))
|
||||
self.log.info("Identified %d SMS in backup data", len(self.results))
|
||||
|
|
|
@ -7,7 +7,6 @@ import os
|
|||
|
||||
from rich.prompt import Prompt
|
||||
|
||||
|
||||
MVT_ANDROID_BACKUP_PASSWORD = "MVT_ANDROID_BACKUP_PASSWORD"
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import logging
|
||||
import sqlite3
|
||||
from typing import Union, Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
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
|
||||
# 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
|
||||
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
|
||||
|
|
|
@ -3,16 +3,21 @@
|
|||
# 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
|
||||
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
|
||||
|
|
|
@ -4,18 +4,22 @@
|
|||
# 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
|
||||
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
|
||||
|
@ -28,6 +32,9 @@ class TestDumpsysPackagesModule:
|
|||
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")
|
||||
|
|
|
@ -3,16 +3,21 @@
|
|||
# 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
|
||||
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
|
||||
|
|
|
@ -4,20 +4,35 @@
|
|||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from mvt.android.modules.androidqf.getprop import Getprop
|
||||
from mvt.common.indicators import Indicators
|
||||
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:
|
||||
def test_androidqf_getprop(self):
|
||||
m = Getprop(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
||||
)
|
||||
data_path = get_android_androidqf()
|
||||
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)
|
||||
assert len(m.results) == 10
|
||||
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||
|
@ -26,9 +41,11 @@ class TestAndroidqfGetpropAnalysis:
|
|||
assert len(m.detected) == 0
|
||||
|
||||
def test_androidqf_getprop_detection(self, indicator_file):
|
||||
m = Getprop(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
||||
)
|
||||
data_path = get_android_androidqf()
|
||||
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.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree")
|
||||
|
|
|
@ -4,19 +4,21 @@
|
|||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from mvt.android.modules.androidqf.processes import Processes
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_artifact_folder
|
||||
from ..utils import get_android_androidqf, list_files
|
||||
|
||||
|
||||
class TestAndroidqfProcessesAnalysis:
|
||||
def test_androidqf_processes(self):
|
||||
m = Processes(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
||||
)
|
||||
data_path = get_android_androidqf()
|
||||
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)
|
||||
assert len(m.results) == 15
|
||||
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
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mvt.android.modules.androidqf.settings import Settings
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_android_androidqf
|
||||
from ..utils import get_android_androidqf, list_files
|
||||
|
||||
|
||||
class TestSettingsModule:
|
||||
def test_parsing(self):
|
||||
data_path = get_android_androidqf()
|
||||
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)
|
||||
assert len(m.results) == 1
|
||||
assert "random" in m.results.keys()
|
||||
|
|
|
@ -5,65 +5,84 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from mvt.android.modules.androidqf.sms import SMS
|
||||
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"
|
||||
|
||||
|
||||
class TestAndroidqfSMSAnalysis:
|
||||
def test_androidqf_sms(self):
|
||||
m = SMS(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging
|
||||
)
|
||||
data_path = get_android_androidqf()
|
||||
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)
|
||||
assert len(m.results) == 2
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
|
||||
def test_androidqf_sms_encrypted_password_valid(self):
|
||||
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||
m = SMS(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
||||
target_path=data_path,
|
||||
log=logging,
|
||||
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)
|
||||
assert len(m.results) == 1
|
||||
|
||||
def test_androidqf_sms_encrypted_password_prompt(self, mocker):
|
||||
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||
prompt_mock = mocker.patch(
|
||||
"rich.prompt.Prompt.ask", return_value=TEST_BACKUP_PASSWORD
|
||||
)
|
||||
m = SMS(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
||||
target_path=data_path,
|
||||
log=logging,
|
||||
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)
|
||||
assert prompt_mock.call_count == 1
|
||||
assert len(m.results) == 1
|
||||
|
||||
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):
|
||||
m = SMS(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
||||
target_path=data_path,
|
||||
log=logging,
|
||||
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)
|
||||
assert len(m.results) == 0
|
||||
assert "Invalid backup password" in caplog.text
|
||||
|
||||
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):
|
||||
m = SMS(
|
||||
target_path=os.path.join(get_artifact_folder(), "androidqf_encrypted"),
|
||||
target_path=data_path,
|
||||
log=logging,
|
||||
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)
|
||||
assert len(m.results) == 0
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
|
@ -12,7 +12,6 @@ from mvt.android.cli import check_backup
|
|||
|
||||
from .utils import get_artifact_folder
|
||||
|
||||
|
||||
TEST_BACKUP_PASSWORD = "123456"
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_artifact(fname):
|
||||
|
@ -34,3 +35,15 @@ def get_android_androidqf():
|
|||
|
||||
def get_indicator_file():
|
||||
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