mirror of https://github.com/mvt-project/mvt.git
Migrate dumpsys_packages parsing into an artifact
This commit is contained in:
parent
54c5d549af
commit
5826e6b11c
|
@ -0,0 +1,203 @@
|
||||||
|
# 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 re
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
|
from mvt.android.utils import ROOT_PACKAGES
|
||||||
|
|
||||||
|
from .artifact import AndroidArtifact
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysPackagesArtifact(AndroidArtifact):
|
||||||
|
def check_indicators(self) -> None:
|
||||||
|
for result in self.results:
|
||||||
|
if result["package_name"] in ROOT_PACKAGES:
|
||||||
|
self.log.warning(
|
||||||
|
"Found an installed package related to "
|
||||||
|
'rooting/jailbreaking: "%s"',
|
||||||
|
result["package_name"],
|
||||||
|
)
|
||||||
|
self.detected.append(result)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not self.indicators:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ioc = self.indicators.check_app_id(result.get("package_name", ""))
|
||||||
|
if ioc:
|
||||||
|
result["matched_indicator"] = ioc
|
||||||
|
self.detected.append(result)
|
||||||
|
|
||||||
|
def serialize(self, record: dict) -> Union[dict, list]:
|
||||||
|
records = []
|
||||||
|
|
||||||
|
timestamps = [
|
||||||
|
{"event": "package_install", "timestamp": record["timestamp"]},
|
||||||
|
{
|
||||||
|
"event": "package_first_install",
|
||||||
|
"timestamp": record["first_install_time"],
|
||||||
|
},
|
||||||
|
{"event": "package_last_update", "timestamp": record["last_update_time"]},
|
||||||
|
]
|
||||||
|
|
||||||
|
for timestamp in timestamps:
|
||||||
|
records.append(
|
||||||
|
{
|
||||||
|
"timestamp": timestamp["timestamp"],
|
||||||
|
"module": self.__class__.__name__,
|
||||||
|
"event": timestamp["event"],
|
||||||
|
"data": f"Install or update of package {record['package_name']}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_dumpsys_package_for_details(output: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Parse one entry of a dumpsys package information
|
||||||
|
"""
|
||||||
|
details = {
|
||||||
|
"uid": "",
|
||||||
|
"version_name": "",
|
||||||
|
"version_code": "",
|
||||||
|
"timestamp": "",
|
||||||
|
"first_install_time": "",
|
||||||
|
"last_update_time": "",
|
||||||
|
"permissions": [],
|
||||||
|
"requested_permissions": [],
|
||||||
|
}
|
||||||
|
in_install_permissions = False
|
||||||
|
in_runtime_permissions = False
|
||||||
|
in_declared_permissions = False
|
||||||
|
in_requested_permissions = True
|
||||||
|
for line in output.splitlines():
|
||||||
|
if in_install_permissions:
|
||||||
|
if line.startswith(" " * 4) and not line.startswith(" " * 6):
|
||||||
|
in_install_permissions = False
|
||||||
|
else:
|
||||||
|
lineinfo = line.strip().split(":")
|
||||||
|
permission = lineinfo[0]
|
||||||
|
granted = None
|
||||||
|
if "granted=" in lineinfo[1]:
|
||||||
|
granted = "granted=true" in lineinfo[1]
|
||||||
|
|
||||||
|
details["permissions"].append(
|
||||||
|
{"name": permission, "granted": granted, "type": "install"}
|
||||||
|
)
|
||||||
|
if in_runtime_permissions:
|
||||||
|
if not line.startswith(" " * 8):
|
||||||
|
in_runtime_permissions = False
|
||||||
|
else:
|
||||||
|
lineinfo = line.strip().split(":")
|
||||||
|
permission = lineinfo[0]
|
||||||
|
granted = None
|
||||||
|
if "granted=" in lineinfo[1]:
|
||||||
|
granted = "granted=true" in lineinfo[1]
|
||||||
|
|
||||||
|
details["permissions"].append(
|
||||||
|
{"name": permission, "granted": granted, "type": "runtime"}
|
||||||
|
)
|
||||||
|
if in_declared_permissions:
|
||||||
|
if not line.startswith(" " * 6):
|
||||||
|
in_declared_permissions = False
|
||||||
|
else:
|
||||||
|
permission = line.strip().split(":")[0]
|
||||||
|
details["permissions"].append(
|
||||||
|
{"name": permission, "type": "declared"}
|
||||||
|
)
|
||||||
|
if in_requested_permissions:
|
||||||
|
if not line.startswith(" " * 6):
|
||||||
|
in_requested_permissions = False
|
||||||
|
else:
|
||||||
|
details["requested_permissions"].append(line.strip())
|
||||||
|
if line.strip().startswith("userId="):
|
||||||
|
details["uid"] = line.split("=")[1].strip()
|
||||||
|
elif line.strip().startswith("versionName="):
|
||||||
|
details["version_name"] = line.split("=")[1].strip()
|
||||||
|
elif line.strip().startswith("versionCode="):
|
||||||
|
details["version_code"] = line.split("=", 1)[1].strip()
|
||||||
|
elif line.strip().startswith("timeStamp="):
|
||||||
|
details["timestamp"] = line.split("=")[1].strip()
|
||||||
|
elif line.strip().startswith("firstInstallTime="):
|
||||||
|
details["first_install_time"] = line.split("=")[1].strip()
|
||||||
|
elif line.strip().startswith("lastUpdateTime="):
|
||||||
|
details["last_update_time"] = line.split("=")[1].strip()
|
||||||
|
elif line.strip() == "install permissions:":
|
||||||
|
in_install_permissions = True
|
||||||
|
elif line.strip() == "runtime permissions:":
|
||||||
|
in_runtime_permissions = True
|
||||||
|
elif line.strip() == "declared permissions:":
|
||||||
|
in_declared_permissions = True
|
||||||
|
elif line.strip() == "requested permissions:":
|
||||||
|
in_requested_permissions = True
|
||||||
|
|
||||||
|
return details
|
||||||
|
|
||||||
|
def parse_dumpsys_packages(self, output: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Parse the dumpsys package service data
|
||||||
|
"""
|
||||||
|
pkg_rxp = re.compile(r" Package \[(.+?)\].*")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
package_name = None
|
||||||
|
package = {}
|
||||||
|
lines = []
|
||||||
|
for line in output.splitlines():
|
||||||
|
if line.startswith(" Package ["):
|
||||||
|
if len(lines) > 0:
|
||||||
|
details = self.parse_dumpsys_package_for_details("\n".join(lines))
|
||||||
|
package.update(details)
|
||||||
|
results.append(package)
|
||||||
|
lines = []
|
||||||
|
package = {}
|
||||||
|
|
||||||
|
matches = pkg_rxp.findall(line)
|
||||||
|
if not matches:
|
||||||
|
continue
|
||||||
|
|
||||||
|
package_name = matches[0]
|
||||||
|
package["package_name"] = package_name
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not package_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
if len(lines) > 0:
|
||||||
|
details = self.parse_dumpsys_package_for_details("\n".join(lines))
|
||||||
|
package.update(details)
|
||||||
|
results.append(package)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def parse(self, content: str):
|
||||||
|
"""
|
||||||
|
Parse the Dumpsys Package section for activities
|
||||||
|
Adds results to self.results
|
||||||
|
|
||||||
|
:param content: content of the package section (string)
|
||||||
|
"""
|
||||||
|
self.results = []
|
||||||
|
package = []
|
||||||
|
|
||||||
|
in_package_list = False
|
||||||
|
for line in content.split("\n"):
|
||||||
|
if line.startswith("Packages:"):
|
||||||
|
in_package_list = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not in_package_list:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.strip() == "":
|
||||||
|
break
|
||||||
|
|
||||||
|
package.append(line)
|
||||||
|
|
||||||
|
self.results = self.parse_dumpsys_packages("\n".join(package))
|
|
@ -4,86 +4,25 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.progress import track
|
from rich.progress import track
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from mvt.android.parsers.dumpsys import parse_dumpsys_package_for_details
|
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
|
||||||
|
from mvt.android.utils import (
|
||||||
|
DANGEROUS_PERMISSIONS,
|
||||||
|
DANGEROUS_PERMISSIONS_THRESHOLD,
|
||||||
|
ROOT_PACKAGES,
|
||||||
|
SECURITY_PACKAGES,
|
||||||
|
SYSTEM_UPDATE_PACKAGES,
|
||||||
|
)
|
||||||
from mvt.common.virustotal import VTNoKey, VTQuotaExceeded, virustotal_lookup
|
from mvt.common.virustotal import VTNoKey, VTQuotaExceeded, virustotal_lookup
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
DANGEROUS_PERMISSIONS_THRESHOLD = 10
|
|
||||||
DANGEROUS_PERMISSIONS = [
|
|
||||||
"android.permission.ACCESS_COARSE_LOCATION",
|
|
||||||
"android.permission.ACCESS_FINE_LOCATION",
|
|
||||||
"android.permission.AUTHENTICATE_ACCOUNTS",
|
|
||||||
"android.permission.CAMERA",
|
|
||||||
"android.permission.DISABLE_KEYGUARD",
|
|
||||||
"android.permission.PROCESS_OUTGOING_CALLS",
|
|
||||||
"android.permission.READ_CALENDAR",
|
|
||||||
"android.permission.READ_CALL_LOG",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.READ_PHONE_STATE",
|
|
||||||
"android.permission.READ_SMS",
|
|
||||||
"android.permission.RECEIVE_MMS",
|
|
||||||
"android.permission.RECEIVE_SMS",
|
|
||||||
"android.permission.RECEIVE_WAP_PUSH",
|
|
||||||
"android.permission.RECORD_AUDIO",
|
|
||||||
"android.permission.SEND_SMS",
|
|
||||||
"android.permission.SYSTEM_ALERT_WINDOW",
|
|
||||||
"android.permission.USE_CREDENTIALS",
|
|
||||||
"android.permission.USE_SIP",
|
|
||||||
"com.android.browser.permission.READ_HISTORY_BOOKMARKS",
|
|
||||||
]
|
|
||||||
ROOT_PACKAGES: List[str] = [
|
|
||||||
"com.noshufou.android.su",
|
|
||||||
"com.noshufou.android.su.elite",
|
|
||||||
"eu.chainfire.supersu",
|
|
||||||
"com.koushikdutta.superuser",
|
|
||||||
"com.thirdparty.superuser",
|
|
||||||
"com.yellowes.su",
|
|
||||||
"com.koushikdutta.rommanager",
|
|
||||||
"com.koushikdutta.rommanager.license",
|
|
||||||
"com.dimonvideo.luckypatcher",
|
|
||||||
"com.chelpus.lackypatch",
|
|
||||||
"com.ramdroid.appquarantine",
|
|
||||||
"com.ramdroid.appquarantinepro",
|
|
||||||
"com.devadvance.rootcloak",
|
|
||||||
"com.devadvance.rootcloakplus",
|
|
||||||
"de.robv.android.xposed.installer",
|
|
||||||
"com.saurik.substrate",
|
|
||||||
"com.zachspong.temprootremovejb",
|
|
||||||
"com.amphoras.hidemyroot",
|
|
||||||
"com.amphoras.hidemyrootadfree",
|
|
||||||
"com.formyhm.hiderootPremium",
|
|
||||||
"com.formyhm.hideroot",
|
|
||||||
"me.phh.superuser",
|
|
||||||
"eu.chainfire.supersu.pro",
|
|
||||||
"com.kingouser.com",
|
|
||||||
"com.topjohnwu.magisk",
|
|
||||||
]
|
|
||||||
SECURITY_PACKAGES = [
|
|
||||||
"com.policydm",
|
|
||||||
"com.samsung.android.app.omcagent",
|
|
||||||
"com.samsung.android.securitylogagent",
|
|
||||||
"com.sec.android.soagent",
|
|
||||||
]
|
|
||||||
SYSTEM_UPDATE_PACKAGES = [
|
|
||||||
"com.android.updater",
|
|
||||||
"com.google.android.gms",
|
|
||||||
"com.huawei.android.hwouc",
|
|
||||||
"com.lge.lgdmsclient",
|
|
||||||
"com.motorola.ccc.ota",
|
|
||||||
"com.oneplus.opbackup",
|
|
||||||
"com.oppo.ota",
|
|
||||||
"com.transsion.systemupdate",
|
|
||||||
"com.wssyncmldm",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Packages(AndroidExtraction):
|
class Packages(AndroidExtraction):
|
||||||
"""This module extracts the list of installed packages."""
|
"""This module extracts the list of installed packages."""
|
||||||
|
@ -234,7 +173,9 @@ class Packages(AndroidExtraction):
|
||||||
if line.strip() == "Packages:":
|
if line.strip() == "Packages:":
|
||||||
in_packages = True
|
in_packages = True
|
||||||
|
|
||||||
return parse_dumpsys_package_for_details("\n".join(lines))
|
return DumpsysPackagesArtifact.parse_dumpsys_package_for_details(
|
||||||
|
"\n".join(lines)
|
||||||
|
)
|
||||||
|
|
||||||
def _get_files_for_package(self, package_name: str) -> list:
|
def _get_files_for_package(self, package_name: str) -> list:
|
||||||
command = f"pm path {package_name}"
|
command = f"pm path {package_name}"
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
|
||||||
from mvt.android.modules.adb.packages import (
|
from mvt.android.modules.adb.packages import (
|
||||||
DANGEROUS_PERMISSIONS,
|
DANGEROUS_PERMISSIONS,
|
||||||
DANGEROUS_PERMISSIONS_THRESHOLD,
|
DANGEROUS_PERMISSIONS_THRESHOLD,
|
||||||
ROOT_PACKAGES,
|
|
||||||
)
|
)
|
||||||
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
|
|
||||||
|
|
||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysPackages(AndroidQFModule):
|
class DumpsysPackages(DumpsysPackagesArtifact, AndroidQFModule):
|
||||||
"""This module analyse dumpsys packages"""
|
"""This module analyse dumpsys packages"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -37,70 +36,15 @@ class DumpsysPackages(AndroidQFModule):
|
||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
def serialize(self, record: dict) -> Union[dict, list]:
|
|
||||||
entries = []
|
|
||||||
for entry in ["timestamp", "first_install_time", "last_update_time"]:
|
|
||||||
if entry in record:
|
|
||||||
entries.append(
|
|
||||||
{
|
|
||||||
"timestamp": record[entry],
|
|
||||||
"module": self.__class__.__name__,
|
|
||||||
"event": entry,
|
|
||||||
"data": f"Package {record['package_name']} "
|
|
||||||
f"({record['uid']})",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
|
||||||
for result in self.results:
|
|
||||||
if result["package_name"] in ROOT_PACKAGES:
|
|
||||||
self.log.warning(
|
|
||||||
"Found an installed package related to "
|
|
||||||
'rooting/jailbreaking: "%s"',
|
|
||||||
result["package_name"],
|
|
||||||
)
|
|
||||||
self.detected.append(result)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not self.indicators:
|
|
||||||
continue
|
|
||||||
|
|
||||||
ioc = self.indicators.check_app_id(result.get("package_name", ""))
|
|
||||||
if ioc:
|
|
||||||
result["matched_indicator"] = ioc
|
|
||||||
self.detected.append(result)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
if len(dumpsys_file) != 1:
|
if len(dumpsys_file) != 1:
|
||||||
self.log.info("Dumpsys file not found")
|
self.log.info("Dumpsys file not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
data = self._get_file_content(dumpsys_file[0])
|
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
|
||||||
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
|
||||||
package = []
|
self.parse(content)
|
||||||
in_service = False
|
|
||||||
in_package_list = False
|
|
||||||
for line in data.decode("utf-8", errors="ignore").split("\n"):
|
|
||||||
if line.strip().startswith("DUMP OF SERVICE package:"):
|
|
||||||
in_service = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_service and line.startswith("Packages:"):
|
|
||||||
in_package_list = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_service or not in_package_list:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.strip() == "":
|
|
||||||
break
|
|
||||||
|
|
||||||
package.append(line)
|
|
||||||
|
|
||||||
self.results = parse_dumpsys_packages("\n".join(package))
|
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
dangerous_permissions_count = 0
|
dangerous_permissions_count = 0
|
||||||
|
|
|
@ -4,19 +4,15 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.modules.adb.packages import (
|
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
|
||||||
DANGEROUS_PERMISSIONS,
|
from mvt.android.utils import DANGEROUS_PERMISSIONS, DANGEROUS_PERMISSIONS_THRESHOLD
|
||||||
DANGEROUS_PERMISSIONS_THRESHOLD,
|
|
||||||
ROOT_PACKAGES,
|
|
||||||
)
|
|
||||||
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
|
|
||||||
|
|
||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class Packages(BugReportModule):
|
class Packages(DumpsysPackagesArtifact, BugReportModule):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -37,83 +33,18 @@ class Packages(BugReportModule):
|
||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
def serialize(self, record: dict) -> Union[dict, list]:
|
|
||||||
records = []
|
|
||||||
|
|
||||||
timestamps = [
|
|
||||||
{"event": "package_install", "timestamp": record["timestamp"]},
|
|
||||||
{
|
|
||||||
"event": "package_first_install",
|
|
||||||
"timestamp": record["first_install_time"],
|
|
||||||
},
|
|
||||||
{"event": "package_last_update", "timestamp": record["last_update_time"]},
|
|
||||||
]
|
|
||||||
|
|
||||||
for timestamp in timestamps:
|
|
||||||
records.append(
|
|
||||||
{
|
|
||||||
"timestamp": timestamp["timestamp"],
|
|
||||||
"module": self.__class__.__name__,
|
|
||||||
"event": timestamp["event"],
|
|
||||||
"data": f"Install or update of package {record['package_name']}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return records
|
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
|
||||||
for result in self.results:
|
|
||||||
if result["package_name"] in ROOT_PACKAGES:
|
|
||||||
self.log.warning(
|
|
||||||
"Found an installed package related to "
|
|
||||||
'rooting/jailbreaking: "%s"',
|
|
||||||
result["package_name"],
|
|
||||||
)
|
|
||||||
self.detected.append(result)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not self.indicators:
|
|
||||||
continue
|
|
||||||
|
|
||||||
ioc = self.indicators.check_app_id(result.get("package_name"))
|
|
||||||
if ioc:
|
|
||||||
result["matched_indicator"] = ioc
|
|
||||||
self.detected.append(result)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
content = self._get_dumpstate_file()
|
data = self._get_dumpstate_file()
|
||||||
if not content:
|
if not data:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
"Unable to find dumpstate file. "
|
"Unable to find dumpstate file. "
|
||||||
"Did you provide a valid bug report archive?"
|
"Did you provide a valid bug report archive?"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
in_package = False
|
data = data.decode("utf-8", errors="replace")
|
||||||
in_packages_list = False
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
|
||||||
lines = []
|
self.parse(content)
|
||||||
for line in content.decode(errors="ignore").splitlines():
|
|
||||||
if line.strip() == "DUMP OF SERVICE package:":
|
|
||||||
in_package = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_package:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.strip() == "Packages:":
|
|
||||||
in_packages_list = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_packages_list:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.strip() == "":
|
|
||||||
break
|
|
||||||
|
|
||||||
lines.append(line)
|
|
||||||
|
|
||||||
self.results = parse_dumpsys_packages("\n".join(lines))
|
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
dangerous_permissions_count = 0
|
dangerous_permissions_count = 0
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
# 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 re
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
|
|
||||||
def parse_dumpsys_package_for_details(output: str) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Parse one entry of a dumpsys package information
|
|
||||||
"""
|
|
||||||
details = {
|
|
||||||
"uid": "",
|
|
||||||
"version_name": "",
|
|
||||||
"version_code": "",
|
|
||||||
"timestamp": "",
|
|
||||||
"first_install_time": "",
|
|
||||||
"last_update_time": "",
|
|
||||||
"permissions": [],
|
|
||||||
"requested_permissions": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
in_install_permissions = False
|
|
||||||
in_runtime_permissions = False
|
|
||||||
in_declared_permissions = False
|
|
||||||
in_requested_permissions = True
|
|
||||||
for line in output.splitlines():
|
|
||||||
if in_install_permissions:
|
|
||||||
if line.startswith(" " * 4) and not line.startswith(" " * 6):
|
|
||||||
in_install_permissions = False
|
|
||||||
else:
|
|
||||||
lineinfo = line.strip().split(":")
|
|
||||||
permission = lineinfo[0]
|
|
||||||
granted = None
|
|
||||||
if "granted=" in lineinfo[1]:
|
|
||||||
granted = "granted=true" in lineinfo[1]
|
|
||||||
|
|
||||||
details["permissions"].append(
|
|
||||||
{"name": permission, "granted": granted, "type": "install"}
|
|
||||||
)
|
|
||||||
|
|
||||||
if in_runtime_permissions:
|
|
||||||
if not line.startswith(" " * 8):
|
|
||||||
in_runtime_permissions = False
|
|
||||||
else:
|
|
||||||
lineinfo = line.strip().split(":")
|
|
||||||
permission = lineinfo[0]
|
|
||||||
granted = None
|
|
||||||
if "granted=" in lineinfo[1]:
|
|
||||||
granted = "granted=true" in lineinfo[1]
|
|
||||||
|
|
||||||
details["permissions"].append(
|
|
||||||
{"name": permission, "granted": granted, "type": "runtime"}
|
|
||||||
)
|
|
||||||
|
|
||||||
if in_declared_permissions:
|
|
||||||
if not line.startswith(" " * 6):
|
|
||||||
in_declared_permissions = False
|
|
||||||
else:
|
|
||||||
permission = line.strip().split(":")[0]
|
|
||||||
details["permissions"].append({"name": permission, "type": "declared"})
|
|
||||||
if in_requested_permissions:
|
|
||||||
if not line.startswith(" " * 6):
|
|
||||||
in_requested_permissions = False
|
|
||||||
else:
|
|
||||||
details["requested_permissions"].append(line.strip())
|
|
||||||
|
|
||||||
if line.strip().startswith("userId="):
|
|
||||||
details["uid"] = line.split("=")[1].strip()
|
|
||||||
elif line.strip().startswith("versionName="):
|
|
||||||
details["version_name"] = line.split("=")[1].strip()
|
|
||||||
elif line.strip().startswith("versionCode="):
|
|
||||||
details["version_code"] = line.split("=", 1)[1].strip()
|
|
||||||
elif line.strip().startswith("timeStamp="):
|
|
||||||
details["timestamp"] = line.split("=")[1].strip()
|
|
||||||
elif line.strip().startswith("firstInstallTime="):
|
|
||||||
details["first_install_time"] = line.split("=")[1].strip()
|
|
||||||
elif line.strip().startswith("lastUpdateTime="):
|
|
||||||
details["last_update_time"] = line.split("=")[1].strip()
|
|
||||||
elif line.strip() == "install permissions:":
|
|
||||||
in_install_permissions = True
|
|
||||||
elif line.strip() == "runtime permissions:":
|
|
||||||
in_runtime_permissions = True
|
|
||||||
elif line.strip() == "declared permissions:":
|
|
||||||
in_declared_permissions = True
|
|
||||||
elif line.strip() == "requested permissions:":
|
|
||||||
in_requested_permissions = True
|
|
||||||
|
|
||||||
return details
|
|
||||||
|
|
||||||
|
|
||||||
def parse_dumpsys_packages(output: str) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Parse the dumpsys package service data
|
|
||||||
"""
|
|
||||||
pkg_rxp = re.compile(r" Package \[(.+?)\].*")
|
|
||||||
|
|
||||||
results = []
|
|
||||||
package_name = None
|
|
||||||
package = {}
|
|
||||||
lines = []
|
|
||||||
for line in output.splitlines():
|
|
||||||
if line.startswith(" Package ["):
|
|
||||||
if len(lines) > 0:
|
|
||||||
details = parse_dumpsys_package_for_details("\n".join(lines))
|
|
||||||
package.update(details)
|
|
||||||
results.append(package)
|
|
||||||
lines = []
|
|
||||||
package = {}
|
|
||||||
|
|
||||||
matches = pkg_rxp.findall(line)
|
|
||||||
if not matches:
|
|
||||||
continue
|
|
||||||
|
|
||||||
package_name = matches[0]
|
|
||||||
package["package_name"] = package_name
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not package_name:
|
|
||||||
continue
|
|
||||||
|
|
||||||
lines.append(line)
|
|
||||||
|
|
||||||
if len(lines) > 0:
|
|
||||||
details = parse_dumpsys_package_for_details("\n".join(lines))
|
|
||||||
package.update(details)
|
|
||||||
results.append(package)
|
|
||||||
|
|
||||||
return results
|
|
|
@ -3,6 +3,7 @@
|
||||||
# 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 datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def warn_android_patch_level(patch_level: str, log) -> bool:
|
def warn_android_patch_level(patch_level: str, log) -> bool:
|
||||||
|
@ -17,3 +18,76 @@ def warn_android_patch_level(patch_level: str, log) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_PACKAGES: List[str] = [
|
||||||
|
"com.noshufou.android.su",
|
||||||
|
"com.noshufou.android.su.elite",
|
||||||
|
"eu.chainfire.supersu",
|
||||||
|
"com.koushikdutta.superuser",
|
||||||
|
"com.thirdparty.superuser",
|
||||||
|
"com.yellowes.su",
|
||||||
|
"com.koushikdutta.rommanager",
|
||||||
|
"com.koushikdutta.rommanager.license",
|
||||||
|
"com.dimonvideo.luckypatcher",
|
||||||
|
"com.chelpus.lackypatch",
|
||||||
|
"com.ramdroid.appquarantine",
|
||||||
|
"com.ramdroid.appquarantinepro",
|
||||||
|
"com.devadvance.rootcloak",
|
||||||
|
"com.devadvance.rootcloakplus",
|
||||||
|
"de.robv.android.xposed.installer",
|
||||||
|
"com.saurik.substrate",
|
||||||
|
"com.zachspong.temprootremovejb",
|
||||||
|
"com.amphoras.hidemyroot",
|
||||||
|
"com.amphoras.hidemyrootadfree",
|
||||||
|
"com.formyhm.hiderootPremium",
|
||||||
|
"com.formyhm.hideroot",
|
||||||
|
"me.phh.superuser",
|
||||||
|
"eu.chainfire.supersu.pro",
|
||||||
|
"com.kingouser.com",
|
||||||
|
"com.topjohnwu.magisk",
|
||||||
|
]
|
||||||
|
|
||||||
|
DANGEROUS_PERMISSIONS_THRESHOLD = 10
|
||||||
|
|
||||||
|
DANGEROUS_PERMISSIONS = [
|
||||||
|
"android.permission.ACCESS_COARSE_LOCATION",
|
||||||
|
"android.permission.ACCESS_FINE_LOCATION",
|
||||||
|
"android.permission.AUTHENTICATE_ACCOUNTS",
|
||||||
|
"android.permission.CAMERA",
|
||||||
|
"android.permission.DISABLE_KEYGUARD",
|
||||||
|
"android.permission.PROCESS_OUTGOING_CALLS",
|
||||||
|
"android.permission.READ_CALENDAR",
|
||||||
|
"android.permission.READ_CALL_LOG",
|
||||||
|
"android.permission.READ_CONTACTS",
|
||||||
|
"android.permission.READ_PHONE_STATE",
|
||||||
|
"android.permission.READ_SMS",
|
||||||
|
"android.permission.RECEIVE_MMS",
|
||||||
|
"android.permission.RECEIVE_SMS",
|
||||||
|
"android.permission.RECEIVE_WAP_PUSH",
|
||||||
|
"android.permission.RECORD_AUDIO",
|
||||||
|
"android.permission.SEND_SMS",
|
||||||
|
"android.permission.SYSTEM_ALERT_WINDOW",
|
||||||
|
"android.permission.USE_CREDENTIALS",
|
||||||
|
"android.permission.USE_SIP",
|
||||||
|
"com.android.browser.permission.READ_HISTORY_BOOKMARKS",
|
||||||
|
]
|
||||||
|
|
||||||
|
SECURITY_PACKAGES = [
|
||||||
|
"com.policydm",
|
||||||
|
"com.samsung.android.app.omcagent",
|
||||||
|
"com.samsung.android.securitylogagent",
|
||||||
|
"com.sec.android.soagent",
|
||||||
|
]
|
||||||
|
|
||||||
|
SYSTEM_UPDATE_PACKAGES = [
|
||||||
|
"com.android.updater",
|
||||||
|
"com.google.android.gms",
|
||||||
|
"com.huawei.android.hwouc",
|
||||||
|
"com.lge.lgdmsclient",
|
||||||
|
"com.motorola.ccc.ota",
|
||||||
|
"com.oneplus.opbackup",
|
||||||
|
"com.oppo.ota",
|
||||||
|
"com.transsion.systemupdate",
|
||||||
|
"com.wssyncmldm",
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# 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 mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
|
||||||
|
from mvt.common.indicators import Indicators
|
||||||
|
|
||||||
|
from ..utils import get_artifact
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysPackagesArtifact:
|
||||||
|
def test_parsing(self):
|
||||||
|
dpa = DumpsysPackagesArtifact()
|
||||||
|
file = get_artifact("android_data/dumpsys_packages.txt")
|
||||||
|
with open(file) as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
assert len(dpa.results) == 0
|
||||||
|
dpa.parse(data)
|
||||||
|
assert len(dpa.results) == 2
|
||||||
|
assert (
|
||||||
|
dpa.results[0]["package_name"]
|
||||||
|
== "com.samsung.android.provider.filterprovider"
|
||||||
|
)
|
||||||
|
assert dpa.results[0]["version_name"] == "5.0.07"
|
||||||
|
|
||||||
|
def test_ioc_check(self, indicator_file):
|
||||||
|
dpa = DumpsysPackagesArtifact()
|
||||||
|
file = get_artifact("android_data/dumpsys_packages.txt")
|
||||||
|
with open(file) as f:
|
||||||
|
data = f.read()
|
||||||
|
dpa.parse(data)
|
||||||
|
|
||||||
|
ind = Indicators(log=logging.getLogger())
|
||||||
|
ind.parse_stix2(indicator_file)
|
||||||
|
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate")
|
||||||
|
dpa.indicators = ind
|
||||||
|
assert len(dpa.detected) == 0
|
||||||
|
dpa.check_indicators()
|
||||||
|
assert len(dpa.detected) == 1
|
|
@ -4,7 +4,6 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
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 mvt.ios.modules.fs.filesystem import Filesystem
|
from mvt.ios.modules.fs.filesystem import Filesystem
|
||||||
|
|
Loading…
Reference in New Issue