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

View File

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

View File

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

View File

@ -72,6 +72,7 @@ class Indicators:
"files_sha256": [], "files_sha256": [],
"app_ids": [], "app_ids": [],
"ios_profile_ids": [], "ios_profile_ids": [],
"android_property_names": [],
"count": 0, "count": 0,
} }
@ -121,6 +122,11 @@ class Indicators:
ioc_coll=collection, ioc_coll=collection,
ioc_coll_list=collection["ios_profile_ids"]) 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: def parse_stix2(self, file_path: str) -> None:
"""Extract indicators from a STIX2 file. """Extract indicators from a STIX2 file.
@ -519,3 +525,23 @@ class Indicators:
return ioc return ioc
return None 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 import os
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.module import run_module from mvt.common.module import run_module
from ..utils import get_artifact_folder 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) m = Getprop(target_path=os.path.join(get_artifact_folder(), "androidqf"), log=logging)
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]["value"] == "lz4"
assert len(m.timeline) == 0 assert len(m.timeline) == 0
assert len(m.detected) == 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"] processes = ["Launch"]
emails = ["foobar@example.org"] emails = ["foobar@example.org"]
filenames = ["/var/foobar/txt"] filenames = ["/var/foobar/txt"]
android_property = ["sys.foobar"]
res = [] res = []
malware = Malware(name="TestMalware", is_family=False, description="") malware = Malware(name="TestMalware", is_family=False, description="")
@ -40,6 +41,11 @@ def generate_test_stix_file(file_path):
res.append(i) res.append(i)
res.append(Relationship(i, "indicates", malware)) 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) bundle = Bundle(objects=res)
with open(file_path, "w+", encoding="utf-8") as f: with open(file_path, "w+", encoding="utf-8") as f:
f.write(bundle.serialize(pretty=True)) f.write(bundle.serialize(pretty=True))

View File

@ -14,11 +14,12 @@ class TestIndicators:
def test_parse_stix2(self, indicator_file): def test_parse_stix2(self, indicator_file):
ind = Indicators(log=logging) ind = Indicators(log=logging)
ind.load_indicators_files([indicator_file], load_default=False) 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]["domains"]) == 1
assert len(ind.ioc_collections[0]["emails"]) == 1 assert len(ind.ioc_collections[0]["emails"]) == 1
assert len(ind.ioc_collections[0]["file_names"]) == 1 assert len(ind.ioc_collections[0]["file_names"]) == 1
assert len(ind.ioc_collections[0]["processes"]) == 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): def test_check_domain(self, indicator_file):
ind = Indicators(log=logging) ind = Indicators(log=logging)
@ -26,8 +27,14 @@ class TestIndicators:
assert ind.check_domain("https://www.example.org/foobar") assert ind.check_domain("https://www.example.org/foobar")
assert ind.check_domain("http://example.org:8080/toto") 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): def test_env_stix(self, indicator_file):
os.environ["MVT_STIX2"] = indicator_file os.environ["MVT_STIX2"] = indicator_file
ind = Indicators(log=logging) ind = Indicators(log=logging)
ind.load_indicators_files([], load_default=False) ind.load_indicators_files([], load_default=False)
assert ind.total_ioc_count == 4 assert ind.total_ioc_count == 5