mirror of
https://github.com/mvt-project/mvt.git
synced 2026-07-03 03:35:49 +02:00
f5b0a3cd91
* Add delay option to virustotal checks (#408) * Fix missing delay argument * Fix VirusTotal delay handling * Fix mypy type for VirusTotal package map --------- Co-authored-by: Janik Besendorf <janik@besendorf.org>
190 lines
6.3 KiB
Python
190 lines
6.3 KiB
Python
# Mobile Verification Toolkit (MVT)
|
|
# Copyright (c) 2021-2023 The MVT Authors.
|
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
|
# https://license.mvt.re/1.1/
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from mvt.android.cli import check_androidqf
|
|
from mvt.android.modules.androidqf.aqf_packages import AQFPackages
|
|
from mvt.android.modules.androidqf import aqf_packages as aqf_packages_module
|
|
from mvt.common.module import run_module
|
|
|
|
from ..utils import get_android_androidqf, list_files
|
|
|
|
|
|
@pytest.fixture()
|
|
def data_path():
|
|
return get_android_androidqf()
|
|
|
|
|
|
@pytest.fixture()
|
|
def parent_data_path(data_path):
|
|
return Path(data_path).absolute().parent.as_posix()
|
|
|
|
|
|
@pytest.fixture()
|
|
def file_list(data_path):
|
|
return list_files(data_path)
|
|
|
|
|
|
@pytest.fixture()
|
|
def module(parent_data_path, file_list):
|
|
m = AQFPackages(target_path=parent_data_path, log=logging)
|
|
m.from_dir(parent_data_path, file_list)
|
|
return m
|
|
|
|
|
|
class TestAndroidqfPackages:
|
|
def test_packages_list(self, module):
|
|
run_module(module)
|
|
|
|
# There should just be 7 packages listed, no detections
|
|
assert len(module.results) == 7
|
|
assert len(module.timeline) == 0
|
|
|
|
def test_non_appstore_warnings(self, caplog, module):
|
|
run_module(module)
|
|
|
|
assert len(module.alertstore.alerts) == 5
|
|
|
|
# Not a super test to be searching logs for this but heuristic detections not yet formalised
|
|
adb_message = "Found a non-system package installed via adb or another method:"
|
|
whatsapp_detected = [
|
|
alert
|
|
for alert in module.alertstore.alerts
|
|
if alert.event["name"] == "com.whatsapp"
|
|
]
|
|
assert len(whatsapp_detected) == 1
|
|
assert adb_message in whatsapp_detected[0].message
|
|
|
|
browser_message = 'Found a package installed via a browser (installer="com.google.android.packageinstaller"): '
|
|
revanced_detected = [
|
|
alert
|
|
for alert in module.alertstore.alerts
|
|
if alert.event["name"] == "app.revanced.manager.flutter"
|
|
]
|
|
assert len(revanced_detected) == 1
|
|
assert browser_message in revanced_detected[0].message
|
|
|
|
third_party_message = 'Found a package installed via a third party store (installer="org.fdroid.fdroid")'
|
|
appollo_detected = [
|
|
alert
|
|
for alert in module.alertstore.alerts
|
|
if alert.event["name"] == "org.nuclearfog.apollo"
|
|
]
|
|
assert len(appollo_detected) == 1
|
|
assert third_party_message in appollo_detected[0].message
|
|
|
|
def test_packages_ioc_package_names(self, module, indicators_factory):
|
|
module.indicators = indicators_factory(app_ids=["com.malware.blah"])
|
|
|
|
run_module(module)
|
|
|
|
possible_detected_app = [
|
|
alert
|
|
for alert in module.alertstore.alerts
|
|
if alert.event["name"] == "com.malware.blah"
|
|
]
|
|
assert len(possible_detected_app) == 1
|
|
assert possible_detected_app[0].event["name"] == "com.malware.blah"
|
|
assert possible_detected_app[0].matched_indicator.value == "com.malware.blah"
|
|
|
|
def test_packages_ioc_sha256(self, module, indicators_factory):
|
|
module.indicators = indicators_factory(
|
|
files_sha256=[
|
|
"31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa"
|
|
]
|
|
)
|
|
|
|
run_module(module)
|
|
|
|
possible_detected_app = [
|
|
alert
|
|
for alert in module.alertstore.alerts
|
|
if alert.event["name"] == "com.malware.muahaha"
|
|
]
|
|
assert len(possible_detected_app) == 1
|
|
assert possible_detected_app[0].event["name"] == "com.malware.muahaha"
|
|
assert (
|
|
possible_detected_app[0].matched_indicator.value
|
|
== "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa"
|
|
)
|
|
|
|
def test_packages_certificate_hash_ioc(self, module, indicators_factory):
|
|
module.indicators = indicators_factory(
|
|
app_cert_hashes=[
|
|
"c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730"
|
|
]
|
|
)
|
|
|
|
run_module(module)
|
|
|
|
possible_detected_app = [
|
|
alert
|
|
for alert in module.alertstore.alerts
|
|
if alert.event["name"] == "com.malware.muahaha"
|
|
]
|
|
assert len(possible_detected_app) == 1
|
|
assert possible_detected_app[0].event["name"] == "com.malware.muahaha"
|
|
assert (
|
|
possible_detected_app[0].matched_indicator.value
|
|
== "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730"
|
|
)
|
|
|
|
def test_virustotal_delays_after_missing_result(self, monkeypatch):
|
|
lookups = []
|
|
sleeps = []
|
|
|
|
def fake_virustotal_lookup(file_hash):
|
|
lookups.append(file_hash)
|
|
if file_hash == "missing_hash":
|
|
return None
|
|
return {
|
|
"attributes": {
|
|
"last_analysis_stats": {"malicious": 1},
|
|
"last_analysis_results": {"engine": {}},
|
|
}
|
|
}
|
|
|
|
monkeypatch.setattr(
|
|
aqf_packages_module, "virustotal_lookup", fake_virustotal_lookup
|
|
)
|
|
monkeypatch.setattr(aqf_packages_module.time, "sleep", sleeps.append)
|
|
|
|
module = AQFPackages(
|
|
module_options={"virustotal": True, "virustotal_delay": 16},
|
|
results=[
|
|
{
|
|
"name": "org.example",
|
|
"installer": "com.android.vending",
|
|
"disabled": False,
|
|
"system": False,
|
|
"files": [
|
|
{"path": "/data/app/missing.apk", "sha256": "missing_hash"},
|
|
{"path": "/data/app/found.apk", "sha256": "found_hash"},
|
|
],
|
|
}
|
|
],
|
|
)
|
|
|
|
module.check_indicators()
|
|
|
|
assert lookups == ["missing_hash", "found_hash"]
|
|
assert sleeps == [16]
|
|
assert module.results[0]["files"][1]["virustotal"] == "1/1"
|
|
assert len(module.alertstore.alerts) == 1
|
|
|
|
|
|
def test_check_androidqf_rejects_negative_virustotal_delay(data_path):
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(check_androidqf, ["--delay", "-1", data_path])
|
|
|
|
assert result.exit_code == 2
|
|
assert "Invalid value for '--delay'" in result.output
|