2021-07-16 06:05:01 +00:00
|
|
|
# Mobile Verification Toolkit (MVT)
|
2023-09-09 15:55:27 +00:00
|
|
|
# Copyright (c) 2021-2023 The MVT Authors.
|
2021-08-01 19:11:08 +00:00
|
|
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
|
|
|
# https://license.mvt.re/1.1/
|
2021-07-16 06:05:01 +00:00
|
|
|
|
|
|
|
import json
|
|
|
|
import logging
|
2021-07-30 09:40:09 +00:00
|
|
|
import os
|
2022-08-16 11:39:55 +00:00
|
|
|
from typing import Callable, Optional
|
2021-07-30 09:40:09 +00:00
|
|
|
|
2022-07-07 10:28:30 +00:00
|
|
|
from rich.progress import track
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2021-08-17 11:06:31 +00:00
|
|
|
from mvt.common.module import InsufficientPrivileges
|
2021-07-30 09:40:09 +00:00
|
|
|
|
2021-07-16 06:05:01 +00:00
|
|
|
from .modules.adb.base import AndroidExtraction
|
2021-08-25 11:35:21 +00:00
|
|
|
from .modules.adb.packages import Packages
|
2021-07-16 06:05:01 +00:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2021-11-19 14:27:51 +00:00
|
|
|
|
2021-07-16 06:05:01 +00:00
|
|
|
class DownloadAPKs(AndroidExtraction):
|
|
|
|
"""DownloadAPKs is the main class operating the download of APKs
|
2021-09-10 13:18:13 +00:00
|
|
|
from the device.
|
|
|
|
"""
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2022-08-16 11:39:55 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-08-17 13:52:17 +00:00
|
|
|
results_path: Optional[str] = None,
|
2023-06-01 21:40:26 +00:00
|
|
|
all_apks: bool = False,
|
2023-04-25 09:13:46 +00:00
|
|
|
packages: Optional[list] = None,
|
2022-08-16 11:39:55 +00:00
|
|
|
) -> None:
|
2021-08-12 16:25:57 +00:00
|
|
|
"""Initialize module.
|
2022-07-06 16:38:16 +00:00
|
|
|
:param results_path: Path to the folder where data should be stored
|
2021-08-12 16:25:57 +00:00
|
|
|
:param all_apks: Boolean indicating whether to download all packages
|
|
|
|
or filter known-goods
|
|
|
|
:param packages: Provided list of packages, typically for JSON checks
|
|
|
|
"""
|
2022-07-06 16:38:16 +00:00
|
|
|
super().__init__(results_path=results_path, log=log)
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2021-08-25 11:35:21 +00:00
|
|
|
self.packages = packages
|
2021-07-16 06:05:01 +00:00
|
|
|
self.all_apks = all_apks
|
2022-07-06 16:38:16 +00:00
|
|
|
self.results_path_apks = None
|
2021-07-16 06:05:01 +00:00
|
|
|
|
|
|
|
@classmethod
|
2022-07-06 16:38:16 +00:00
|
|
|
def from_json(cls, json_path: str) -> Callable:
|
2021-08-17 11:26:26 +00:00
|
|
|
"""Initialize this class from an existing apks.json file.
|
2021-09-10 13:18:13 +00:00
|
|
|
|
2021-08-17 11:26:26 +00:00
|
|
|
:param json_path: Path to the apks.json file to parse.
|
2021-09-10 13:18:13 +00:00
|
|
|
|
2021-07-16 06:05:01 +00:00
|
|
|
"""
|
2022-01-29 11:18:18 +00:00
|
|
|
with open(json_path, "r", encoding="utf-8") as handle:
|
2021-08-25 11:35:21 +00:00
|
|
|
packages = json.load(handle)
|
2021-07-16 06:05:01 +00:00
|
|
|
return cls(packages=packages)
|
|
|
|
|
2022-07-06 16:38:16 +00:00
|
|
|
def pull_package_file(self, package_name: str, remote_path: str) -> None:
|
2021-07-16 06:05:01 +00:00
|
|
|
"""Pull files related to specific package from the device.
|
2021-09-10 13:18:13 +00:00
|
|
|
|
2021-07-16 06:05:01 +00:00
|
|
|
:param package_name: Name of the package to download
|
|
|
|
:param remote_path: Path to the file to download
|
|
|
|
:returns: Path to the local copy
|
2021-09-10 13:18:13 +00:00
|
|
|
|
2021-07-16 06:05:01 +00:00
|
|
|
"""
|
|
|
|
log.info("Downloading %s ...", remote_path)
|
|
|
|
|
|
|
|
file_name = ""
|
|
|
|
if "==/" in remote_path:
|
|
|
|
file_name = "_" + remote_path.split("==/")[1].replace(".apk", "")
|
|
|
|
|
2023-06-01 21:40:26 +00:00
|
|
|
local_path = os.path.join(
|
|
|
|
self.results_path_apks, f"{package_name}{file_name}.apk"
|
|
|
|
)
|
2021-07-16 06:05:01 +00:00
|
|
|
name_counter = 0
|
|
|
|
while True:
|
|
|
|
if not os.path.exists(local_path):
|
|
|
|
break
|
|
|
|
|
|
|
|
name_counter += 1
|
2023-06-01 21:40:26 +00:00
|
|
|
local_path = os.path.join(
|
|
|
|
self.results_path_apks, f"{package_name}{file_name}_{name_counter}.apk"
|
|
|
|
)
|
2021-07-16 06:05:01 +00:00
|
|
|
|
|
|
|
try:
|
2022-07-07 10:28:30 +00:00
|
|
|
self._adb_download(remote_path, local_path)
|
2021-08-17 11:06:31 +00:00
|
|
|
except InsufficientPrivileges:
|
2023-06-01 21:40:26 +00:00
|
|
|
log.error(
|
|
|
|
"Unable to pull package file from %s: insufficient privileges, "
|
|
|
|
"it might be a system app",
|
|
|
|
remote_path,
|
|
|
|
)
|
2021-08-17 11:06:31 +00:00
|
|
|
self._adb_reconnect()
|
|
|
|
return None
|
2022-08-13 00:14:24 +00:00
|
|
|
except Exception as exc:
|
2023-06-01 21:40:26 +00:00
|
|
|
log.exception("Failed to pull package file from %s: %s", remote_path, exc)
|
2021-07-16 06:05:01 +00:00
|
|
|
self._adb_reconnect()
|
|
|
|
return None
|
|
|
|
|
|
|
|
return local_path
|
|
|
|
|
2022-07-06 16:38:16 +00:00
|
|
|
def get_packages(self) -> None:
|
2021-08-25 11:35:21 +00:00
|
|
|
"""Use the Packages adb module to retrieve the list of packages.
|
|
|
|
We reuse the same extraction logic to then download the APKs.
|
|
|
|
"""
|
|
|
|
self.log.info("Retrieving list of installed packages...")
|
|
|
|
|
|
|
|
m = Packages()
|
|
|
|
m.log = self.log
|
2022-07-02 14:14:27 +00:00
|
|
|
m.serial = self.serial
|
2021-08-25 11:35:21 +00:00
|
|
|
m.run()
|
|
|
|
|
|
|
|
self.packages = m.results
|
|
|
|
|
2022-07-06 16:38:16 +00:00
|
|
|
def pull_packages(self) -> None:
|
2023-06-01 21:40:26 +00:00
|
|
|
"""Download all files of all selected packages from the device."""
|
|
|
|
log.info(
|
|
|
|
"Starting extraction of installed APKs at folder %s", self.results_path
|
|
|
|
)
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2021-08-25 11:35:21 +00:00
|
|
|
# If the user provided the flag --all-apks we select all packages.
|
|
|
|
packages_selection = []
|
|
|
|
if self.all_apks:
|
|
|
|
log.info("Selected all %d available packages", len(self.packages))
|
|
|
|
packages_selection = self.packages
|
|
|
|
else:
|
|
|
|
# Otherwise we loop through the packages and get only those that
|
|
|
|
# are not marked as system.
|
|
|
|
for package in self.packages:
|
|
|
|
if not package.get("system", False):
|
|
|
|
packages_selection.append(package)
|
|
|
|
|
2023-06-01 21:40:26 +00:00
|
|
|
log.info(
|
|
|
|
'Selected only %d packages which are not marked as "system"',
|
|
|
|
len(packages_selection),
|
|
|
|
)
|
2021-08-25 11:35:21 +00:00
|
|
|
|
|
|
|
if len(packages_selection) == 0:
|
|
|
|
log.info("No packages were selected for download")
|
|
|
|
return
|
|
|
|
|
2021-07-16 06:05:01 +00:00
|
|
|
log.info("Downloading packages from device. This might take some time ...")
|
|
|
|
|
2022-07-06 16:38:16 +00:00
|
|
|
self.results_path_apks = os.path.join(self.results_path, "apks")
|
|
|
|
if not os.path.exists(self.results_path_apks):
|
2022-07-07 10:28:30 +00:00
|
|
|
os.makedirs(self.results_path_apks, exist_ok=True)
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2023-06-01 21:40:26 +00:00
|
|
|
for i in track(
|
|
|
|
range(len(packages_selection)),
|
|
|
|
description=f"Downloading {len(packages_selection)} packages...",
|
|
|
|
):
|
2022-07-07 10:28:30 +00:00
|
|
|
package = packages_selection[i]
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2023-06-01 21:40:26 +00:00
|
|
|
log.info(
|
|
|
|
"[%d/%d] Package: %s",
|
|
|
|
i,
|
|
|
|
len(packages_selection),
|
|
|
|
package["package_name"],
|
|
|
|
)
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2022-08-13 15:51:06 +00:00
|
|
|
# Sometimes the package path contains multiple lines for multiple
|
|
|
|
# apks. We loop through each line and download each file.
|
2021-09-20 17:15:39 +00:00
|
|
|
for package_file in package["files"]:
|
|
|
|
device_path = package_file["path"]
|
2023-06-01 21:40:26 +00:00
|
|
|
local_path = self.pull_package_file(
|
|
|
|
package["package_name"], device_path
|
|
|
|
)
|
2021-09-20 17:15:39 +00:00
|
|
|
if not local_path:
|
2021-07-16 06:05:01 +00:00
|
|
|
continue
|
|
|
|
|
2021-09-20 17:15:39 +00:00
|
|
|
package_file["local_path"] = local_path
|
2021-08-25 11:35:21 +00:00
|
|
|
|
|
|
|
log.info("Download of selected packages completed")
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2022-07-06 16:38:16 +00:00
|
|
|
def save_json(self) -> None:
|
|
|
|
json_path = os.path.join(self.results_path, "apks.json")
|
2022-01-29 11:18:18 +00:00
|
|
|
with open(json_path, "w", encoding="utf-8") as handle:
|
2021-08-25 11:35:21 +00:00
|
|
|
json.dump(self.packages, handle, indent=4)
|
2021-07-16 06:05:01 +00:00
|
|
|
|
2022-06-17 20:30:46 +00:00
|
|
|
def run(self) -> None:
|
2021-07-16 06:05:01 +00:00
|
|
|
self.get_packages()
|
2021-08-25 11:35:21 +00:00
|
|
|
self._adb_connect()
|
2021-07-16 06:05:01 +00:00
|
|
|
self.pull_packages()
|
|
|
|
self.save_json()
|
|
|
|
self._adb_disconnect()
|