From cb6bde5b8c256eca24ed3357996e64f05a51f6dd Mon Sep 17 00:00:00 2001 From: Adam Lawson Date: Tue, 20 Jul 2021 12:43:54 +0100 Subject: [PATCH 1/2] Fix download of APKs that require root privileges Some system APKs are stored in directories that require root privileges, such as /system/product. --- mvt/android/download_apks.py | 2 +- mvt/android/modules/adb/base.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/mvt/android/download_apks.py b/mvt/android/download_apks.py index 5dd4cbc..b670411 100644 --- a/mvt/android/download_apks.py +++ b/mvt/android/download_apks.py @@ -135,7 +135,7 @@ class DownloadAPKs(AndroidExtraction): try: with PullProgress(unit='B', unit_divisor=1024, unit_scale=True, miniters=1) as pp: - self._adb_download(remote_path, local_path, + self._adb_download(remote_path, local_path, package_name, progress_callback=pp.update_to) except Exception as e: log.exception("Failed to pull package file from %s: %s", diff --git a/mvt/android/modules/adb/base.py b/mvt/android/modules/adb/base.py index 31aa931..3c9e9b1 100644 --- a/mvt/android/modules/adb/base.py +++ b/mvt/android/modules/adb/base.py @@ -111,7 +111,7 @@ class AndroidExtraction(MVTModule): """ return self._adb_command(f"su -c {command}") - def _adb_download(self, remote_path, local_path, progress_callback=None): + def _adb_download(self, remote_path, local_path, package_name, progress_callback=None): """Download a file form the device. :param remote_path: Path to download from the device :param local_path: Path to where to locally store the copy of the file @@ -119,8 +119,35 @@ class AndroidExtraction(MVTModule): """ try: self.device.pull(remote_path, local_path, progress_callback) + except AdbCommandFailureException: + self._adb_download_root(remote_path, local_path, package_name, progress_callback) except AdbCommandFailureException as e: raise Exception(f"Unable to download file {remote_path}: {e}") + + def _adb_download_root(self, remote_path, local_path, package_name, progress_callback=None): + try: + # Check if we have root, if not raise an Exception. + self._adb_root_or_die() + + # We create a temporary local file. + new_remote_path = f"/sdcard/Download/{package_name}" + + # We copy the file from the data folder to /sdcard/. + cp = self._adb_command_as_root(f"cp {remote_path} {new_remote_path}") + if cp.startswith("cp: ") and "No such file or directory" in cp: + raise Exception(f"Unable to process file {remote_path}: File not found") + elif cp.startswith("cp: ") and "Permission denied" in cp: + raise Exception(f"Unable to process file {remote_path}: Permission denied") + + # We download from /sdcard/ to the local temporary file. + self._adb_download(new_remote_path, local_path, package_name) + + # Delete the copy on /sdcard/. + self._adb_command(f"rm -f {new_remote_path}") + + except AdbCommandFailureException as e: + raise Exception(f"Unable to download file {remote_path}: {e}") + def _adb_process_file(self, remote_path, process_routine): """Download a local copy of a file which is only accessible as root. From 03523a40c07516796fa5f7f442fad30e35969794 Mon Sep 17 00:00:00 2001 From: Trigus42 Date: Sat, 24 Jul 2021 12:09:59 +0200 Subject: [PATCH 2/2] Fix _adb_process_file & Improve _adb_download_root - The _adb_download function doesn't need a package_name argument. This broke _adb_process_file and unnecessarily clutters function calls. Also, the function may be used to download other files or folders too. Generating a random filename seems like the best solution to me since it is less likely to get a duplicate filename and thus to replace an existing file. - The path /sdcard/Download doesn't necessarily exist. Using /sdcard seems more reliable. --- mvt/android/download_apks.py | 2 +- mvt/android/modules/adb/base.py | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/mvt/android/download_apks.py b/mvt/android/download_apks.py index b670411..5dd4cbc 100644 --- a/mvt/android/download_apks.py +++ b/mvt/android/download_apks.py @@ -135,7 +135,7 @@ class DownloadAPKs(AndroidExtraction): try: with PullProgress(unit='B', unit_divisor=1024, unit_scale=True, miniters=1) as pp: - self._adb_download(remote_path, local_path, package_name, + self._adb_download(remote_path, local_path, progress_callback=pp.update_to) except Exception as e: log.exception("Failed to pull package file from %s: %s", diff --git a/mvt/android/modules/adb/base.py b/mvt/android/modules/adb/base.py index 3c9e9b1..67139e1 100644 --- a/mvt/android/modules/adb/base.py +++ b/mvt/android/modules/adb/base.py @@ -4,6 +4,8 @@ # https://github.com/mvt-project/mvt/blob/main/LICENSE import os +import random +import string import sys import time import logging @@ -111,7 +113,7 @@ class AndroidExtraction(MVTModule): """ return self._adb_command(f"su -c {command}") - def _adb_download(self, remote_path, local_path, package_name, progress_callback=None): + def _adb_download(self, remote_path, local_path, progress_callback=None, retry_root=True): """Download a file form the device. :param remote_path: Path to download from the device :param local_path: Path to where to locally store the copy of the file @@ -119,18 +121,22 @@ class AndroidExtraction(MVTModule): """ try: self.device.pull(remote_path, local_path, progress_callback) - except AdbCommandFailureException: - self._adb_download_root(remote_path, local_path, package_name, progress_callback) except AdbCommandFailureException as e: - raise Exception(f"Unable to download file {remote_path}: {e}") + if retry_root: + self._adb_download_root(remote_path, local_path, progress_callback) + else: + raise Exception(f"Unable to download file {remote_path}: {e}") - def _adb_download_root(self, remote_path, local_path, package_name, progress_callback=None): + def _adb_download_root(self, remote_path, local_path, progress_callback=None): try: # Check if we have root, if not raise an Exception. self._adb_root_or_die() + # We generate a random temporary filename. + tmp_filename = "tmp_" + ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=10)) + # We create a temporary local file. - new_remote_path = f"/sdcard/Download/{package_name}" + new_remote_path = f"/sdcard/{tmp_filename}" # We copy the file from the data folder to /sdcard/. cp = self._adb_command_as_root(f"cp {remote_path} {new_remote_path}") @@ -140,14 +146,14 @@ class AndroidExtraction(MVTModule): raise Exception(f"Unable to process file {remote_path}: Permission denied") # We download from /sdcard/ to the local temporary file. - self._adb_download(new_remote_path, local_path, package_name) + # If it doesn't work now, don't try again (retry_root=False) + self._adb_download(new_remote_path, local_path, retry_root=False) # Delete the copy on /sdcard/. - self._adb_command(f"rm -f {new_remote_path}") - + self._adb_command(f"rm -rf {new_remote_path}") + except AdbCommandFailureException as e: raise Exception(f"Unable to download file {remote_path}: {e}") - def _adb_process_file(self, remote_path, process_routine): """Download a local copy of a file which is only accessible as root.