Adds indicators for android properties

This commit is contained in:
tek 2023-03-29 12:57:41 +02:00
parent 70c6f0c153
commit b5d7e528de
7 changed files with 96 additions and 19 deletions

View File

@ -30,6 +30,16 @@ class Getprop(AndroidExtraction):
self.results = {} if not results else results
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
ioc = self.indicators.check_android_property_name(result.get("name", ""))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("getprop")
@ -38,13 +48,14 @@ class Getprop(AndroidExtraction):
self.results = parse_getprop(output)
# Alert if phone is outdated.
security_patch = self.results.get("ro.build.version.security_patch", "")
if security_patch:
patch_date = datetime.strptime(security_patch, "%Y-%m-%d")
for entry in self.results:
if entry.get("name", "") != "ro.build.version.security_patch":
continue
patch_date = datetime.strptime(entry["value"], "%Y-%m-%d")
if (datetime.now() - patch_date) > timedelta(days=6*30):
self.log.warning("This phone has not received security updates "
"for more than six months (last update: %s)",
security_patch)
entry["value"])
self.log.info("Extracted %d Android system properties",
len(self.results))

View File

@ -7,7 +7,7 @@ import logging
from datetime import datetime, timedelta
from typing import Optional
from mvt.android.parsers import getprop
from mvt.android.parsers.getprop import parse_getprop
from .base import AndroidQFModule
@ -41,7 +41,17 @@ class Getprop(AndroidQFModule):
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
self.results = {}
self.results = []
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
ioc = self.indicators.check_android_property_name(result.get("name", ""))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def run(self) -> None:
getprop_files = self._get_files_by_pattern("*/getprop.txt")
@ -52,15 +62,15 @@ class Getprop(AndroidQFModule):
with open(getprop_files[0]) as f:
data = f.read()
self.results = getprop.parse_getprop(data)
self.results = parse_getprop(data)
for entry in self.results:
if entry in INTERESTING_PROPERTIES:
self.log.info("%s: %s", entry, self.results[entry])
if entry == "ro.build.version.security_patch":
last_patch = datetime.strptime(self.results[entry], "%Y-%m-%d")
if entry["name"] in INTERESTING_PROPERTIES:
self.log.info("%s: %s", entry["name"], entry["value"])
if entry["name"] == "ro.build.version.security_patch":
last_patch = datetime.strptime(entry["value"], "%Y-%m-%d")
if (datetime.now() - last_patch) > timedelta(days=6*31):
self.log.warning("This phone has not received security "
"updates for more than six months "
"(last update: %s)", self.results[entry])
"(last update: %s)", entry["value"])
self.log.info("Extracted a total of %d properties", len(self.results))

View File

@ -4,10 +4,11 @@
# https://license.mvt.re/1.1/
import re
from typing import Dict, List
def parse_getprop(output: str) -> dict:
results = {}
def parse_getprop(output: str) -> List[Dict[str, str]]:
results = []
rxp = re.compile(r"\[(.+?)\]: \[(.+?)\]")
for line in output.splitlines():
@ -19,8 +20,10 @@ def parse_getprop(output: str) -> dict:
if not matches or len(matches[0]) != 2:
continue
key = matches[0][0]
value = matches[0][1]
results[key] = value
entry = {
"name": matches[0][0],
"value": matches[0][1]
}
results.append(entry)
return results

View File

@ -72,6 +72,7 @@ class Indicators:
"files_sha256": [],
"app_ids": [],
"ios_profile_ids": [],
"android_property_names": [],
"count": 0,
}
@ -121,6 +122,11 @@ class Indicators:
ioc_coll=collection,
ioc_coll_list=collection["ios_profile_ids"])
elif key == "android-property:name":
self._add_indicator(ioc=value,
ioc_coll=collection,
ioc_coll_list=collection["android_property_names"])
def parse_stix2(self, file_path: str) -> None:
"""Extract indicators from a STIX2 file.
@ -519,3 +525,23 @@ class Indicators:
return ioc
return None
def check_android_property_name(self, property_name: str) -> Optional[dict]:
"""Check the android property name against the list of indicators.
:param property_name: Name of the Android property
:type property_name: str
:returns: Indicator details if matched, otherwise None
"""
if property_name is None:
return None
for ioc in self.get_iocs("android_property_names"):
if property_name.lower() == ioc["value"].lower():
self.log.warning("Found a known suspicious Android property \"%s\" "
"matching indicators from \"%s\"", property_name,
ioc["name"])
return ioc
return None

View File

@ -7,6 +7,7 @@ import logging
import os
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
@ -18,5 +19,18 @@ class TestAndroidqfGetpropAnalysis:
m = Getprop(target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging)
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_androidqf_getprop_detection(self, indicator_file):
m = Getprop(target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree")
m.indicators = ind
run_module(m)
assert len(m.results) == 10
assert len(m.detected) == 1
assert m.detected[0]["name"] == "dalvik.vm.heapmaxfree"

View File

@ -16,6 +16,7 @@ def generate_test_stix_file(file_path):
processes = ["Launch"]
emails = ["foobar@example.org"]
filenames = ["/var/foobar/txt"]
android_property = ["sys.foobar"]
res = []
malware = Malware(name="TestMalware", is_family=False, description="")
@ -40,6 +41,11 @@ def generate_test_stix_file(file_path):
res.append(i)
res.append(Relationship(i, "indicates", malware))
for p in android_property:
i = Indicator(indicator_types=["malicious-activity"], pattern="[android-property:name='{}']".format(p), pattern_type="stix")
res.append(i)
res.append(Relationship(i, "indicates", malware))
bundle = Bundle(objects=res)
with open(file_path, "w+", encoding="utf-8") as f:
f.write(bundle.serialize(pretty=True))

View File

@ -14,11 +14,12 @@ class TestIndicators:
def test_parse_stix2(self, indicator_file):
ind = Indicators(log=logging)
ind.load_indicators_files([indicator_file], load_default=False)
assert ind.ioc_collections[0]["count"] == 4
assert ind.ioc_collections[0]["count"] == 5
assert len(ind.ioc_collections[0]["domains"]) == 1
assert len(ind.ioc_collections[0]["emails"]) == 1
assert len(ind.ioc_collections[0]["file_names"]) == 1
assert len(ind.ioc_collections[0]["processes"]) == 1
assert len(ind.ioc_collections[0]["android_property_names"]) == 1
def test_check_domain(self, indicator_file):
ind = Indicators(log=logging)
@ -26,8 +27,14 @@ class TestIndicators:
assert ind.check_domain("https://www.example.org/foobar")
assert ind.check_domain("http://example.org:8080/toto")
def test_check_android_property(self, indicator_file):
ind = Indicators(log=logging)
ind.load_indicators_files([indicator_file], load_default=False)
assert ind.check_android_property_name("sys.foobar")
assert ind.check_android_property_name("sys.soundsokay") is None
def test_env_stix(self, indicator_file):
os.environ["MVT_STIX2"] = indicator_file
ind = Indicators(log=logging)
ind.load_indicators_files([], load_default=False)
assert ind.total_ioc_count == 4
assert ind.total_ioc_count == 5