Adds support for zip files in check-androidqf command

This commit is contained in:
tek 2023-07-24 00:57:01 +02:00
parent 15ce1b7e64
commit cfd0baaab0
28 changed files with 235 additions and 124 deletions

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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))

View File

@ -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))

View File

@ -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))

View File

@ -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

View File

@ -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))

View File

@ -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:

View File

@ -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))

View File

@ -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()])

View File

@ -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))

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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()

View File

@ -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 (

Binary file not shown.

View File

@ -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"

View File

@ -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"

View File

@ -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