diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..c63164e --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +pytest>=6.2.5 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/artifacts/0d/0d609c54856a9bb2d56729df1d68f2958a88426b b/tests/artifacts/0d/0d609c54856a9bb2d56729df1d68f2958a88426b new file mode 100644 index 0000000..240d01e Binary files /dev/null and b/tests/artifacts/0d/0d609c54856a9bb2d56729df1d68f2958a88426b differ diff --git a/tests/artifacts/64/64d0019cb3d46bfc8cce545a8ba54b93e7ea9347 b/tests/artifacts/64/64d0019cb3d46bfc8cce545a8ba54b93e7ea9347 new file mode 100644 index 0000000..3a1a64c Binary files /dev/null and b/tests/artifacts/64/64d0019cb3d46bfc8cce545a8ba54b93e7ea9347 differ diff --git a/tests/artifacts/Info.plist b/tests/artifacts/Info.plist new file mode 100644 index 0000000..d5cc569 Binary files /dev/null and b/tests/artifacts/Info.plist differ diff --git a/tests/artifacts/Manifest.db b/tests/artifacts/Manifest.db new file mode 100644 index 0000000..d85bde8 Binary files /dev/null and b/tests/artifacts/Manifest.db differ diff --git a/tests/artifacts/generate_stix.py b/tests/artifacts/generate_stix.py new file mode 100644 index 0000000..9f35d6a --- /dev/null +++ b/tests/artifacts/generate_stix.py @@ -0,0 +1,41 @@ +import sys +import os +from stix2.v21 import (Indicator, Malware, Relationship, Bundle, DomainName) + + +if __name__ == "__main__": + if os.path.isfile("test.stix2"): + os.remove("test.stix2") + + domains = ["example.org"] + processes = ["Launch"] + emails = ["foobar@example.org"] + filenames = ["/var/foobar/txt"] + + res = [] + malware = Malware(name="TestMalware", is_family=False, description="") + res.append(malware) + for d in domains: + i = Indicator(indicator_types=["malicious-activity"], pattern="[domain-name:value='{}']".format(d), pattern_type="stix") + res.append(i) + res.append(Relationship(i, 'indicates', malware)) + + for p in processes: + i = Indicator(indicator_types=["malicious-activity"], pattern="[process:name='{}']".format(p), pattern_type="stix") + res.append(i) + res.append(Relationship(i, 'indicates', malware)) + + for f in filenames: + i = Indicator(indicator_types=["malicious-activity"], pattern="[file:name='{}']".format(f), pattern_type="stix") + res.append(i) + res.append(Relationship(i, 'indicates', malware)) + + for e in emails: + i = Indicator(indicator_types=["malicious-activity"], pattern="[email-addr:value='{}']".format(e), pattern_type="stix") + res.append(i) + res.append(Relationship(i, 'indicates', malware)) + + bundle = Bundle(objects=res) + with open("test.stix2", "w+") as f: + f.write(bundle.serialize(pretty=True)) + print("test.stix2 file created") diff --git a/tests/artifacts/test.stix2 b/tests/artifacts/test.stix2 new file mode 100644 index 0000000..0204786 --- /dev/null +++ b/tests/artifacts/test.stix2 @@ -0,0 +1,112 @@ +{ + "type": "bundle", + "id": "bundle--25fa4351-8a0a-4fea-bb4c-88ecbd0dfbf2", + "objects": [ + { + "type": "malware", + "spec_version": "2.1", + "id": "malware--b4581613-1fe9-441a-a7a5-56df36664e54", + "created": "2021-12-16T11:49:29.897487Z", + "modified": "2021-12-16T11:49:29.897487Z", + "name": "TestMalware", + "description": "", + "is_family": false + }, + { + "type": "indicator", + "spec_version": "2.1", + "id": "indicator--8614e326-7863-4d79-902c-89a0b769f290", + "created": "2021-12-16T11:49:29.897624Z", + "modified": "2021-12-16T11:49:29.897624Z", + "indicator_types": [ + "malicious-activity" + ], + "pattern": "[domain-name:value='example.org']", + "pattern_type": "stix", + "pattern_version": "2.1", + "valid_from": "2021-12-16T11:49:29.897624Z" + }, + { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--6e02e776-1aa7-4436-8df0-d6cb6227f098", + "created": "2021-12-16T11:49:29.903846Z", + "modified": "2021-12-16T11:49:29.903846Z", + "relationship_type": "indicates", + "source_ref": "indicator--8614e326-7863-4d79-902c-89a0b769f290", + "target_ref": "malware--b4581613-1fe9-441a-a7a5-56df36664e54" + }, + { + "type": "indicator", + "spec_version": "2.1", + "id": "indicator--1917e54e-d91d-4d11-811c-79e861c31661", + "created": "2021-12-16T11:49:29.903984Z", + "modified": "2021-12-16T11:49:29.903984Z", + "indicator_types": [ + "malicious-activity" + ], + "pattern": "[process:name='Launch']", + "pattern_type": "stix", + "pattern_version": "2.1", + "valid_from": "2021-12-16T11:49:29.903984Z" + }, + { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--e6561236-ef2e-45ed-984b-d1c4832119ca", + "created": "2021-12-16T11:49:29.905442Z", + "modified": "2021-12-16T11:49:29.905442Z", + "relationship_type": "indicates", + "source_ref": "indicator--1917e54e-d91d-4d11-811c-79e861c31661", + "target_ref": "malware--b4581613-1fe9-441a-a7a5-56df36664e54" + }, + { + "type": "indicator", + "spec_version": "2.1", + "id": "indicator--bd3961ab-e13a-42f5-b677-a797ced82adf", + "created": "2021-12-16T11:49:29.905565Z", + "modified": "2021-12-16T11:49:29.905565Z", + "indicator_types": [ + "malicious-activity" + ], + "pattern": "[file:name='/var/foobar/txt']", + "pattern_type": "stix", + "pattern_version": "2.1", + "valid_from": "2021-12-16T11:49:29.905565Z" + }, + { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--9f4b5ee9-45d1-4b55-877c-082104baedab", + "created": "2021-12-16T11:49:29.906687Z", + "modified": "2021-12-16T11:49:29.906687Z", + "relationship_type": "indicates", + "source_ref": "indicator--bd3961ab-e13a-42f5-b677-a797ced82adf", + "target_ref": "malware--b4581613-1fe9-441a-a7a5-56df36664e54" + }, + { + "type": "indicator", + "spec_version": "2.1", + "id": "indicator--0a798fe3-2293-4e60-8d45-d8d5cbd3f22f", + "created": "2021-12-16T11:49:29.906826Z", + "modified": "2021-12-16T11:49:29.906826Z", + "indicator_types": [ + "malicious-activity" + ], + "pattern": "[email-addr:value='foobar@example.org']", + "pattern_type": "stix", + "pattern_version": "2.1", + "valid_from": "2021-12-16T11:49:29.906826Z" + }, + { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--40f7462d-173b-43b5-b9e3-056f28c01ff7", + "created": "2021-12-16T11:49:29.907909Z", + "modified": "2021-12-16T11:49:29.907909Z", + "relationship_type": "indicates", + "source_ref": "indicator--0a798fe3-2293-4e60-8d45-d8d5cbd3f22f", + "target_ref": "malware--b4581613-1fe9-441a-a7a5-56df36664e54" + } + ] +} \ No newline at end of file diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/common/test_indicators.py b/tests/common/test_indicators.py new file mode 100644 index 0000000..c69e740 --- /dev/null +++ b/tests/common/test_indicators.py @@ -0,0 +1,34 @@ +import pytest +import logging +import os +from ..utils import get_artifact, init_setup +from mvt.common.indicators import Indicators, IndicatorsFileBadFormat + + +class TestIndicators: + @pytest.fixture(scope="session", autouse=True) + def set(self): + init_setup() + + def test_parse_stix2(self): + stix_path = get_artifact("test.stix2") + ind = Indicators(log=logging) + ind.parse_stix2(stix_path) + assert ind.ioc_count == 4 + assert len(ind.ioc_domains) == 1 + assert len(ind.ioc_emails) == 1 + assert len(ind.ioc_files) == 1 + assert len(ind.ioc_processes) == 1 + + def test_check_domain(self): + ind = Indicators(log=logging) + stix_path = get_artifact("test.stix2") + ind.parse_stix2(stix_path) + assert ind.check_domain("https://www.example.org/foobar") == True + assert ind.check_domain("http://example.org:8080/toto") == True + + def test_env_stix(self): + stix_path = get_artifact("test.stix2") + os.environ["MVT_STIX2"] = stix_path + ind = Indicators(log=logging) + assert ind.ioc_count == 4 diff --git a/tests/ios/__init__.py b/tests/ios/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/ios/test_backup_info.py b/tests/ios/test_backup_info.py new file mode 100644 index 0000000..539c787 --- /dev/null +++ b/tests/ios/test_backup_info.py @@ -0,0 +1,20 @@ +import pytest +import logging +import os +from ..utils import get_artifact, get_artifact_folder, init_setup +from mvt.common.indicators import Indicators, IndicatorsFileBadFormat +from mvt.ios.modules.backup.backup_info import BackupInfo +from mvt.common.module import run_module + + +class TestBackupInfoModule: + @pytest.fixture(scope="session", autouse=True) + def set(self): + init_setup() + + def test_manifest(self): + m = BackupInfo(base_folder=get_artifact_folder(), log=logging) + run_module(m) + assert m.results["Build Version"] == "18C66" + assert m.results["IMEI"] == '42' + diff --git a/tests/ios/test_datausage.py b/tests/ios/test_datausage.py new file mode 100644 index 0000000..88e2592 --- /dev/null +++ b/tests/ios/test_datausage.py @@ -0,0 +1,32 @@ +import pytest +import logging +import os +from ..utils import get_artifact, get_artifact_folder, init_setup +from mvt.common.indicators import Indicators, IndicatorsFileBadFormat +from mvt.ios.modules.mixed.net_datausage import Datausage +from mvt.common.module import run_module + + +class TestDatausageModule: + @pytest.fixture(scope="session", autouse=True) + def set(self): + init_setup() + + def test_datausage(self): + m = Datausage(base_folder=get_artifact_folder(), log=logging) + run_module(m) + assert len(m.results) == 42 + assert len(m.timeline) == 60 + assert len(m.detected) == 0 + + def test_detection(self): + m = Datausage(base_folder=get_artifact_folder(), log=logging) + ind = Indicators(log=logging) + ind.parse_stix2(get_artifact("test.stix2")) + # Adds a file that exists in the manifest + ind.ioc_processes[0] = "CumulativeUsageTracker" + m.indicators = ind + run_module(m) + assert len(m.detected) == 4 + + diff --git a/tests/ios/test_manifest.py b/tests/ios/test_manifest.py new file mode 100644 index 0000000..e02efbc --- /dev/null +++ b/tests/ios/test_manifest.py @@ -0,0 +1,32 @@ +import pytest +import logging +import os +from ..utils import get_artifact, get_artifact_folder, init_setup +from mvt.common.indicators import Indicators, IndicatorsFileBadFormat +from mvt.ios.modules.backup.manifest import Manifest +from mvt.common.module import run_module + + +class TestManifestModule: + @pytest.fixture(scope="session", autouse=True) + def set(self): + init_setup() + + def test_manifest(self): + m = Manifest(base_folder=get_artifact_folder(), log=logging) + run_module(m) + assert len(m.results) == 3721 + assert len(m.timeline) == 5881 + assert len(m.detected) == 0 + + def test_detection(self): + m = Manifest(base_folder=get_artifact_folder(), log=logging) + ind = Indicators(log=logging) + ind.parse_stix2(get_artifact("test.stix2")) + # Adds a file that exists in the manifest + ind.ioc_files[0] = "com.apple.CoreBrightness.plist" + m.indicators = ind + run_module(m) + assert len(m.detected) == 2 + + diff --git a/tests/ios/test_tcc.py b/tests/ios/test_tcc.py new file mode 100644 index 0000000..432cb4c --- /dev/null +++ b/tests/ios/test_tcc.py @@ -0,0 +1,23 @@ +import pytest +import logging +import os +from ..utils import get_artifact_folder, init_setup +from mvt.ios.modules.mixed.tcc import TCC +from mvt.common.module import run_module + + +class TestManifestModule: + @pytest.fixture(scope="session", autouse=True) + def set(self): + init_setup() + + def test_manifest(self): + m = TCC(base_folder=get_artifact_folder(), log=logging) + run_module(m) + assert len(m.results) == 11 + # FIXME: TCC should suport timeline + assert len(m.timeline) == 0 + assert len(m.detected) == 0 + assert m.results[0]["service"] == "kTCCServiceUbiquity" + assert m.results[0]["auth_value"] == "allowed" + diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..d31023c --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,23 @@ +import os + +def get_artifact(fname): + """ + Return the artifact path in the artifact folder + """ + DATA_FOLDER = os.path.join(os.path.dirname(__file__), "artifacts") + fpath = os.path.join(DATA_FOLDER, fname) + if os.path.isfile(fpath): + return fpath + return + +def get_artifact_folder(): + return os.path.join(os.path.dirname(__file__), "artifacts") + +def init_setup(): + """ + init data to have a clean state before testing + """ + try: + del os.environ['MVT_STIX2'] + except KeyError: + pass