mvt/mvt/android/download_apks.py

186 lines
6.4 KiB
Python
Raw Normal View History

2021-07-16 06:05:01 +00:00
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 The MVT Project Authors.
# 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
2021-07-16 06:05:01 +00:00
from tqdm import tqdm
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
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
# TODO: Would be better to replace tqdm with rich.progress to reduce
# the number of dependencies. Need to investigate whether
2021-07-22 21:21:31 +00:00
# it's possible to have a similar callback system.
2021-07-16 06:05:01 +00:00
class PullProgress(tqdm):
"""PullProgress is a tqdm update system for APK downloads."""
def update_to(self, file_name, current, total):
if total is not None:
self.total = total
self.update(current - self.n)
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
def __init__(self, output_folder=None, all_apks=False, log=None,
packages=None):
2021-08-12 16:25:57 +00:00
"""Initialize module.
:param output_folder: Path to the folder where data should be stored
:param all_apks: Boolean indicating whether to download all packages
or filter known-goods
:param packages: Provided list of packages, typically for JSON checks
"""
super().__init__(output_folder=output_folder, log=log)
2021-07-16 06:05:01 +00:00
self.packages = packages
2021-07-16 06:05:01 +00:00
self.all_apks = all_apks
self.output_folder_apk = None
2021-07-16 06:05:01 +00:00
@classmethod
def from_json(cls, json_path):
"""Initialize this class from an existing apks.json file.
2021-09-10 13:18:13 +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
"""
with open(json_path, "r") as handle:
packages = json.load(handle)
2021-07-16 06:05:01 +00:00
return cls(packages=packages)
def pull_package_file(self, package_name, remote_path):
"""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", "")
local_path = os.path.join(self.output_folder_apk,
f"{package_name}{file_name}.apk")
name_counter = 0
while True:
if not os.path.exists(local_path):
break
name_counter += 1
local_path = os.path.join(self.output_folder_apk,
f"{package_name}{file_name}_{name_counter}.apk")
try:
with PullProgress(unit='B', unit_divisor=1024, unit_scale=True,
miniters=1) as pp:
self._adb_download(remote_path, local_path,
progress_callback=pp.update_to)
except InsufficientPrivileges:
log.warn("Unable to pull package file from %s: insufficient privileges, it might be a system app",
remote_path)
self._adb_reconnect()
return None
2021-07-16 06:05:01 +00:00
except Exception as e:
log.exception("Failed to pull package file from %s: %s",
remote_path, e)
self._adb_reconnect()
return None
return local_path
def get_packages(self):
"""Use the Packages adb module to retrieve the list of packages.
We reuse the same extraction logic to then download the APKs.
2021-09-10 13:18:13 +00:00
"""
self.log.info("Retrieving list of installed packages...")
m = Packages()
m.log = self.log
m.run()
self.packages = m.results
2021-07-16 06:05:01 +00:00
def pull_packages(self):
2021-09-10 13:18:13 +00:00
"""Download all files of all selected packages from the device."""
2021-07-16 06:05:01 +00:00
log.info("Starting extraction of installed APKs at folder %s", self.output_folder)
if not os.path.exists(self.output_folder):
os.mkdir(self.output_folder)
# 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)
log.info("Selected only %d packages which are not marked as system",
2021-11-19 14:27:51 +00:00
len(packages_selection))
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 ...")
self.output_folder_apk = os.path.join(self.output_folder, "apks")
if not os.path.exists(self.output_folder_apk):
os.mkdir(self.output_folder_apk)
counter = 0
for package in packages_selection:
2021-07-16 06:05:01 +00:00
counter += 1
log.info("[%d/%d] Package: %s", counter, len(packages_selection),
package["package_name"])
2021-07-16 06:05:01 +00:00
# Sometimes the package path contains multiple lines for multiple apks.
# We loop through each line and download each file.
for package_file in package["files"]:
device_path = package_file["path"]
local_path = self.pull_package_file(package["package_name"],
device_path)
if not local_path:
2021-07-16 06:05:01 +00:00
continue
package_file["local_path"] = local_path
log.info("Download of selected packages completed")
2021-07-16 06:05:01 +00:00
def save_json(self):
2021-09-10 13:18:13 +00:00
"""Save the results to the package.json file."""
json_path = os.path.join(self.output_folder, "apks.json")
2021-07-16 06:05:01 +00:00
with open(json_path, "w") as handle:
json.dump(self.packages, handle, indent=4)
2021-07-16 06:05:01 +00:00
def run(self):
2021-09-10 13:18:13 +00:00
"""Run all steps of fetch-apk."""
2021-07-16 06:05:01 +00:00
self.get_packages()
self._adb_connect()
2021-07-16 06:05:01 +00:00
self.pull_packages()
self.save_json()
self._adb_disconnect()