Standardized code with flake8

This commit is contained in:
Nex 2021-11-19 15:27:51 +01:00
parent b3a464ba58
commit 512f40dcb4
63 changed files with 127 additions and 65 deletions

View File

@ -9,7 +9,9 @@ import os
import click
from rich.logging import RichHandler
from mvt.common.help import *
from mvt.common.help import HELP_MSG_MODULE, HELP_MSG_IOC
from mvt.common.help import HELP_MSG_OUTPUT, HELP_MSG_LIST_MODULES
from mvt.common.help import HELP_MSG_SERIAL
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
from mvt.common.logo import logo
from mvt.common.module import run_module, save_timeline
@ -26,6 +28,7 @@ logging.basicConfig(level="INFO", format=LOG_FORMAT, handlers=[
RichHandler(show_path=False, log_time_format="%X")])
log = logging.getLogger(__name__)
#==============================================================================
# Main
#==============================================================================
@ -191,7 +194,7 @@ def check_backup(ctx, iocs, output, backup_path, serial):
log.critical("The path you specified is a not a folder!")
if os.path.basename(backup_path) == "backup.ab":
log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) " \
log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) "
"to extract 'backup.ab' files!")
ctx.exit(1)

View File

@ -16,6 +16,7 @@ from .modules.adb.packages import Packages
log = logging.getLogger(__name__)
# TODO: Would be better to replace tqdm with rich.progress to reduce
# the number of dependencies. Need to investigate whether
# it's possible to have a similar callback system.

View File

@ -13,6 +13,7 @@ from rich.text import Text
log = logging.getLogger(__name__)
def koodous_lookup(packages):
log.info("Looking up all extracted files on Koodous (www.koodous.com)")
log.info("This might take a while...")

View File

@ -13,6 +13,7 @@ from rich.text import Text
log = logging.getLogger(__name__)
def get_virustotal_report(hashes):
apikey = "233f22e200ca5822bd91103043ccac138b910db79f29af5616a9afe8b6f215ad"
url = f"https://www.virustotal.com/partners/sysinternals/file-reports?apikey={apikey}"
@ -36,6 +37,7 @@ def get_virustotal_report(hashes):
log.error("Unexpected response from VirusTotal: %s", res.status_code)
return None
def virustotal_lookup(packages):
log.info("Looking up all extracted files on VirusTotal (www.virustotal.com)")
@ -48,6 +50,7 @@ def virustotal_lookup(packages):
total_unique_hashes = len(unique_hashes)
detections = {}
def virustotal_query(batch):
report = get_virustotal_report(batch)
if not report:

View File

@ -25,6 +25,7 @@ log = logging.getLogger(__name__)
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
ADB_PUB_KEY_PATH = os.path.expanduser("~/.android/adbkey.pub")
class AndroidExtraction(MVTModule):
"""This class provides a base for all Android extraction modules."""

View File

@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
CHROME_HISTORY_PATH = "data/data/com.android.chrome/app_chrome/Default/History"
class ChromeHistory(AndroidExtraction):
"""This module extracts records from Android's Chrome browsing history."""

View File

@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class DumpsysBatterystats(AndroidExtraction):
"""This module extracts stats on battery consumption by processes."""

View File

@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class DumpsysFull(AndroidExtraction):
"""This module extracts stats on battery consumption by processes."""

View File

@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class DumpsysProcstats(AndroidExtraction):
"""This module extracts stats on memory consumption by processes."""

View File

@ -14,6 +14,7 @@ ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
ACTION_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
ACTION_PHONE_STATE = "android.intent.action.PHONE_STATE"
class DumpsysReceivers(AndroidExtraction):
"""This module extracts details on receivers for risky activities."""

View File

@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class Files(AndroidExtraction):
"""This module extracts the list of installed packages."""

View File

@ -12,6 +12,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class Packages(AndroidExtraction):
"""This module extracts the list of installed packages."""
@ -49,7 +50,6 @@ class Packages(AndroidExtraction):
root_packages = root_packages_string.decode("utf-8").split("\n")
root_packages = [rp.strip() for rp in root_packages]
for result in self.results:
if result["package_name"] in root_packages:
self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"",

View File

@ -9,6 +9,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class Processes(AndroidExtraction):
"""This module extracts details on running processes."""

View File

@ -12,6 +12,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__)
class RootBinaries(AndroidExtraction):
"""This module extracts the list of installed packages."""

View File

@ -39,6 +39,7 @@ SELECT
FROM sms;
"""
class SMS(AndroidExtraction):
"""This module extracts all SMS messages containing links."""
@ -62,7 +63,7 @@ class SMS(AndroidExtraction):
return
for message in self.results:
if not "text" in message:
if "text" not in message:
continue
message_links = check_for_links(message["text"])

View File

@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
WHATSAPP_PATH = "data/data/com.whatsapp/databases/msgstore.db"
class Whatsapp(AndroidExtraction):
"""This module extracts all WhatsApp messages containing links."""
@ -39,7 +40,7 @@ class Whatsapp(AndroidExtraction):
return
for message in self.results:
if not "data" in message:
if "data" not in message:
continue
message_links = check_for_links(message["data"])

View File

@ -5,4 +5,4 @@
from .sms import SMS
BACKUP_MODULES = [SMS,]
BACKUP_MODULES = [SMS]

View File

@ -24,7 +24,7 @@ class SMS(MVTModule):
return
for message in self.results:
if not "body" in message:
if "body" not in message:
continue
message_links = check_for_links(message["body"])

View File

@ -12,6 +12,7 @@ from .url import URL
class IndicatorsFileBadFormat(Exception):
pass
class Indicators:
"""This class is used to parse indicators from a STIX2 file and provide
functions to compare extracted artifacts to the indicators.
@ -115,7 +116,7 @@ class Indicators:
else:
# If it's not shortened, we just use the original URL object.
final_url = orig_url
except Exception as e:
except Exception:
# If URL parsing failed, we just try to do a simple substring
# match.
for ioc in self.ioc_domains:

View File

@ -16,7 +16,7 @@ def logo():
try:
latest_version = check_for_updates()
except:
except Exception:
pass
else:
if latest_version:

View File

@ -14,12 +14,15 @@ import simplejson as json
class DatabaseNotFoundError(Exception):
pass
class DatabaseCorruptedError(Exception):
pass
class InsufficientPrivileges(Exception):
pass
class MVTModule(object):
"""This class provides a base for all extraction modules."""

View File

@ -250,6 +250,7 @@ SHORTENER_DOMAINS = [
"zz.gd",
]
class URL:
def __init__(self, url):
@ -273,7 +274,7 @@ class URL:
# TODO: Properly handle exception.
try:
return get_tld(self.url, as_object=True, fix_protocol=True).parsed_url.netloc.lower().lstrip("www.")
except:
except Exception:
return None
def get_top_level(self):
@ -288,7 +289,7 @@ class URL:
# TODO: Properly handle exception.
try:
return get_tld(self.url, as_object=True, fix_protocol=True).fld.lower()
except:
except Exception:
return None
def check_if_shortened(self) -> bool:

View File

@ -45,7 +45,7 @@ def convert_chrometime_to_unix(timestamp):
:returns: Unix epoch timestamp.
"""
epoch_start = datetime.datetime(1601, 1 , 1)
epoch_start = datetime.datetime(1601, 1, 1)
delta = datetime.timedelta(microseconds=timestamp)
return epoch_start + delta
@ -64,6 +64,7 @@ def convert_timestamp_to_iso(timestamp):
except Exception:
return None
def check_for_links(text):
"""Checks if a given text contains HTTP links.
@ -74,6 +75,7 @@ def check_for_links(text):
"""
return re.findall("(?P<url>https?://[^\s]+)", text, re.IGNORECASE)
def get_sha256_from_file_path(file_path):
"""Calculate the SHA256 hash of a file from a file path.
@ -88,6 +90,7 @@ def get_sha256_from_file_path(file_path):
return sha256_hash.hexdigest()
# Note: taken from here:
# https://stackoverflow.com/questions/57014259/json-dumps-on-dictionary-with-bytes-for-keys
def keys_bytes_to_string(obj):

View File

@ -8,6 +8,7 @@ from packaging import version
MVT_VERSION = "1.2.14"
def check_for_updates():
res = requests.get("https://pypi.org/pypi/mvt/json")
data = res.json()

View File

@ -10,7 +10,9 @@ import click
from rich.logging import RichHandler
from rich.prompt import Prompt
from mvt.common.help import *
from mvt.common.help import HELP_MSG_MODULE, HELP_MSG_IOC
from mvt.common.help import HELP_MSG_FAST, HELP_MSG_OUTPUT
from mvt.common.help import HELP_MSG_LIST_MODULES
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
from mvt.common.logo import logo
from mvt.common.module import run_module, save_timeline
@ -30,6 +32,7 @@ log = logging.getLogger(__name__)
# Set this environment variable to a password if needed.
PASSWD_ENV = "MVT_IOS_BACKUP_PASSWORD"
#==============================================================================
# Main
#==============================================================================

View File

@ -14,6 +14,7 @@ from iOSbackup import iOSbackup
log = logging.getLogger(__name__)
class DecryptBackup:
"""This class provides functions to decrypt an encrypted iTunes backup
using either a password or a key file.

View File

@ -10,6 +10,7 @@ from ..base import IOSExtraction
CONF_PROFILES_DOMAIN = "SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles"
class ConfigurationProfiles(IOSExtraction):
"""This module extracts the full plist data from configuration profiles."""

View File

@ -28,7 +28,7 @@ class Manifest(IOSExtraction):
"""Unserialized plist objects can have keys which are str or byte types
This is a helper to try fetch a key as both a byte or string type.
:param dictionary: param key:
:param dictionary:
:param key:
"""
@ -72,7 +72,7 @@ class Manifest(IOSExtraction):
return
for result in self.results:
if not "relative_path" in result:
if "relative_path" not in result:
continue
if not result["relative_path"]:
continue
@ -133,7 +133,7 @@ class Manifest(IOSExtraction):
"owner": self._get_key(file_metadata, "UserID"),
"size": self._get_key(file_metadata, "Size"),
})
except:
except Exception:
self.log.exception("Error reading manifest file metadata for file with ID %s and relative path %s",
file_data["fileID"], file_data["relativePath"])
pass

View File

@ -11,6 +11,7 @@ from ..base import IOSExtraction
CONF_PROFILES_EVENTS_RELPATH = "Library/ConfigurationProfiles/MCProfileEvents.plist"
class ProfileEvents(IOSExtraction):
"""This module extracts events related to the installation of configuration
profiles.

View File

@ -16,4 +16,4 @@ from .webkit_safariviewservice import WebkitSafariViewService
FS_MODULES = [CacheFiles, Filesystem, Netusage, Analytics, SafariFavicon, ShutdownLog,
IOSVersionHistory, WebkitIndexedDB, WebkitLocalStorage,
WebkitSafariViewService,]
WebkitSafariViewService]

View File

@ -14,6 +14,7 @@ ANALYTICS_DB_PATH = [
"private/var/Keychains/Analytics/*.db",
]
class Analytics(IOSExtraction):
"""This module extracts information from the private/var/Keychains/Analytics/*.db files."""
@ -87,7 +88,6 @@ class Analytics(IOSExtraction):
FROM soft_failures;
""")
for row in cur:
if row[0] and row[1]:
timestamp = convert_timestamp_to_iso(convert_mactime_to_unix(row[0], False))

View File

@ -38,7 +38,7 @@ class CacheFiles(IOSExtraction):
for item in items:
if self.indicators.check_domain(item["url"]):
if key not in self.detected:
self.detected[key] = [item,]
self.detected[key] = [item, ]
else:
self.detected[key].append(item)
@ -54,7 +54,7 @@ class CacheFiles(IOSExtraction):
return
key_name = os.path.relpath(file_path, self.base_folder)
if not key_name in self.results:
if key_name not in self.results:
self.results[key_name] = []
for row in cur:

View File

@ -60,7 +60,7 @@ class Filesystem(IOSExtraction):
"path": os.path.relpath(dir_path, self.base_folder),
"modified": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(dir_path).st_mtime)),
}
except:
except Exception:
continue
else:
self.results.append(result)
@ -72,7 +72,7 @@ class Filesystem(IOSExtraction):
"path": os.path.relpath(file_path, self.base_folder),
"modified": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(file_path).st_mtime)),
}
except:
except Exception:
continue
else:
self.results.append(result)

View File

@ -12,6 +12,7 @@ NETUSAGE_ROOT_PATHS = [
"private/var/networkd/db/netusage.sqlite"
]
class Netusage(NetBase):
"""This class extracts data from netusage.sqlite and attempts to identify
any suspicious processes if running on a full filesystem dump.

View File

@ -14,6 +14,7 @@ SAFARI_FAVICON_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/Image Cache/Favicons/Favicons.db",
]
class SafariFavicon(IOSExtraction):
"""This module extracts all Safari favicon records."""

View File

@ -11,6 +11,7 @@ SHUTDOWN_LOG_PATH = [
"private/var/db/diagnostics/shutdown.log",
]
class ShutdownLog(IOSExtraction):
"""This module extracts processes information from the shutdown log file."""
@ -57,7 +58,7 @@ class ShutdownLog(IOSExtraction):
try:
start = line.find(" @")+2
mac_timestamp = int(line[start:start+10])
except:
except Exception:
mac_timestamp = 0
timestamp = convert_mactime_to_unix(mac_timestamp, from_2001=False)

View File

@ -14,6 +14,7 @@ IOS_ANALYTICS_JOURNAL_PATHS = [
"private/var/db/analyticsd/Analytics-Journal-*.ips",
]
class IOSVersionHistory(IOSExtraction):
"""This module extracts iOS update history from Analytics Journal log files."""

View File

@ -9,6 +9,7 @@ WEBKIT_INDEXEDDB_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/IndexedDB",
]
class WebkitIndexedDB(WebkitBase):
"""This module looks extracts records from WebKit IndexedDB folders,
and checks them against any provided list of suspicious domains.

View File

@ -9,6 +9,7 @@ WEBKIT_LOCALSTORAGE_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/LocalStorage/",
]
class WebkitLocalStorage(WebkitBase):
"""This module looks extracts records from WebKit LocalStorage folders,
and checks them against any provided list of suspicious domains.

View File

@ -9,6 +9,7 @@ WEBKIT_SAFARIVIEWSERVICE_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/",
]
class WebkitSafariViewService(WebkitBase):
"""This module looks extracts records from WebKit LocalStorage folders,
and checks them against any provided list of suspicious domains.

View File

@ -27,4 +27,4 @@ MIXED_MODULES = [Calls, ChromeFavicon, ChromeHistory, Contacts, FirefoxFavicon,
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
TCC, SMS, SMSAttachments, WebkitResourceLoadStatistics,
WebkitSessionResourceLog, Whatsapp,]
WebkitSessionResourceLog, Whatsapp]

View File

@ -16,6 +16,7 @@ CALLS_ROOT_PATHS = [
"private/var/mobile/Library/CallHistoryDB/CallHistory.storedata"
]
class Calls(IOSExtraction):
"""This module extracts phone calls details"""
@ -45,7 +46,7 @@ class Calls(IOSExtraction):
ZDATE, ZDURATION, ZLOCATION, ZADDRESS, ZSERVICE_PROVIDER
FROM ZCALLRECORD;
""")
names = [description[0] for description in cur.description]
# names = [description[0] for description in cur.description]
for row in cur:
self.results.append({

View File

@ -19,6 +19,7 @@ CHROME_FAVICON_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/Application Support/Google/Chrome/Default/Favicons",
]
class ChromeFavicon(IOSExtraction):
"""This module extracts all Chrome favicon records."""

View File

@ -13,12 +13,12 @@ from ..base import IOSExtraction
CHROME_HISTORY_BACKUP_IDS = [
"faf971ce92c3ac508c018dce1bef2a8b8e9838f1",
]
# TODO: Confirm Chrome database path.
CHROME_HISTORY_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/Application Support/Google/Chrome/Default/History",
]
class ChromeHistory(IOSExtraction):
"""This module extracts all Chome visits."""

View File

@ -14,6 +14,7 @@ CONTACTS_ROOT_PATHS = [
"private/var/mobile/Library/AddressBook/AddressBook.sqlitedb",
]
class Contacts(IOSExtraction):
"""This module extracts all contact details from the phone's address book."""

View File

@ -17,6 +17,7 @@ FIREFOX_HISTORY_ROOT_PATHS = [
"private/var/mobile/profile.profile/browser.db",
]
class FirefoxFavicon(IOSExtraction):
"""This module extracts all Firefox favicon"""

View File

@ -17,6 +17,7 @@ FIREFOX_HISTORY_ROOT_PATHS = [
"private/var/mobile/profile.profile/browser.db",
]
class FirefoxHistory(IOSExtraction):
"""This module extracts all Firefox visits and tries to detect potential
network injection attacks.

View File

@ -18,6 +18,7 @@ IDSTATUSCACHE_ROOT_PATHS = [
"private/var/mobile/Library/IdentityServices/idstatuscache.plist",
]
class IDStatusCache(IOSExtraction):
"""Extracts Apple Authentication information from idstatuscache.plist"""

View File

@ -16,6 +16,7 @@ INTERACTIONC_ROOT_PATHS = [
"private/var/mobile/Library/CoreDuet/People/interactionC.db",
]
class InteractionC(IOSExtraction):
"""This module extracts data from InteractionC db."""
@ -54,8 +55,8 @@ class InteractionC(IOSExtraction):
"timestamp": record[ts],
"module": self.__class__.__name__,
"event": ts,
"data": f"[{record['bundle_id']}] {record['account']} - from {record['sender_display_name']} " \
f"({record['sender_identifier']}) to {record['recipient_display_name']} " \
"data": f"[{record['bundle_id']}] {record['account']} - from {record['sender_display_name']} "
f"({record['sender_identifier']}) to {record['recipient_display_name']} "
f"({record['recipient_identifier']}): {record['content']}"
})
processed.append(record[ts])
@ -123,8 +124,7 @@ class InteractionC(IOSExtraction):
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK;
""")
names = [description[0] for description in cur.description]
# names = [description[0] for description in cur.description]
for row in cur:
self.results.append({

View File

@ -17,6 +17,7 @@ LOCATIOND_ROOT_PATHS = [
"private/var/root/Library/Caches/locationd/clients.plist"
]
class LocationdClients(IOSExtraction):
"""Extract information from apps who used geolocation."""

View File

@ -12,6 +12,7 @@ DATAUSAGE_ROOT_PATHS = [
"private/var/wireless/Library/Databases/DataUsage.sqlite",
]
class Datausage(NetBase):
"""This class extracts data from DataUsage.sqlite and attempts to identify
any suspicious processes if running on a full filesystem dump.

View File

@ -16,6 +16,7 @@ OSANALYTICS_ADDAILY_ROOT_PATHS = [
"private/var/mobile/Library/Preferences/com.apple.osanalytics.addaily.plist",
]
class OSAnalyticsADDaily(IOSExtraction):
"""Extract network usage information by process, from com.apple.osanalytics.addaily.plist"""

View File

@ -19,6 +19,7 @@ SAFARI_BROWSER_STATE_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/Safari/BrowserState.db",
]
class SafariBrowserState(IOSExtraction):
"""This module extracts all Safari browser state records."""
@ -47,7 +48,7 @@ class SafariBrowserState(IOSExtraction):
self.detected.append(result)
continue
if not "session_data" in result:
if "session_data" not in result:
continue
for session_entry in result["session_data"]:

View File

@ -17,6 +17,7 @@ SAFARI_HISTORY_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/Safari/History.db",
]
class SafariHistory(IOSExtraction):
"""This module extracts all Safari visits and tries to detect potential
network injection attacks.

View File

@ -18,6 +18,7 @@ SMS_ROOT_PATHS = [
"private/var/mobile/Library/SMS/sms.db",
]
class SMS(IOSExtraction):
"""This module extracts all SMS messages containing links."""

View File

@ -17,6 +17,7 @@ SMS_ROOT_PATHS = [
"private/var/mobile/Library/SMS/sms.db",
]
class SMSAttachments(IOSExtraction):
"""This module extracts all info about SMS/iMessage attachments."""

View File

@ -38,6 +38,7 @@ AUTH_REASONS = {
12: "app_type_policy",
}
class TCC(IOSExtraction):
"""This module extracts records from the TCC.db SQLite database."""

View File

@ -17,6 +17,7 @@ WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/observations.db",
]
class WebkitResourceLoadStatistics(IOSExtraction):
"""This module extracts records from WebKit ResourceLoadStatistics observations.db."""
# TODO: Add serialize().
@ -38,7 +39,7 @@ class WebkitResourceLoadStatistics(IOSExtraction):
for item in items:
if self.indicators.check_domain(item["registrable_domain"]):
if key not in self.detected:
self.detected[key] = [item,]
self.detected[key] = [item, ]
else:
self.detected[key].append(item)
@ -55,7 +56,7 @@ class WebkitResourceLoadStatistics(IOSExtraction):
except sqlite3.OperationalError:
return
if not key in self.results:
if key not in self.results:
self.results[key] = []
for row in cur:

View File

@ -20,6 +20,7 @@ WEBKIT_SESSION_RESOURCE_LOG_ROOT_PATHS = [
"private/var/mobile/Library/WebClips/*/Storage/full_browsing_session_resourceLog.plist",
]
class WebkitSessionResourceLog(IOSExtraction):
"""This module extracts records from WebKit browsing session
resource logs, and checks them against any provided list of

View File

@ -20,6 +20,7 @@ WHATSAPP_ROOT_PATHS = [
"private/var/mobile/Containers/Shared/AppGroup/*/ChatStorage.sqlite",
]
class Whatsapp(IOSExtraction):
"""This module extracts all WhatsApp messages containing links."""

View File

@ -233,11 +233,13 @@ IPHONE_IOS_VERSIONS = [
{"build": "19B74", "version": "15.1"},
]
def get_device_desc_from_id(identifier, devices_list=IPHONE_MODELS):
for model in IPHONE_MODELS:
if identifier == model["identifier"]:
return model["description"]
def find_version_by_build(build):
build = build.upper()
for version in IPHONE_IOS_VERSIONS:

View File

@ -30,6 +30,7 @@ requires = (
"libusb1>=2.0.1",
)
def get_package_data(package):
walk = [(dirpath.replace(package + os.sep, "", 1), filenames)
for dirpath, dirnames, filenames in os.walk(package)
@ -41,6 +42,7 @@ def get_package_data(package):
for filename in filenames])
return {package: filepaths}
setup(
name="mvt",
version=MVT_VERSION,