Skip to content

Commit

Permalink
Merge pull request #191 from leminlimez/v4.1
Browse files Browse the repository at this point in the history
v4.1
  • Loading branch information
leminlimez authored Nov 16, 2024
2 parents 49641e4 + 0a1213e commit c92de7a
Show file tree
Hide file tree
Showing 12 changed files with 1,618 additions and 482 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Sparserestore works on all versions iOS 17.0-17.7 and iOS 18.0-18.1 beta 4. Ther

**iOS 18.2 developer beta 3 (public beta 2) and newer is not supported.**

This uses the sparserestore exploit to write to files outside of the intended restore location, like mobilegestalt.
This uses the sparserestore exploit to write to files outside of the intended restore location, like mobilegestalt. Read the [Getting the File](#getting-the-file) section to learn how to get your mobilegestalt file.

Note: I am not responsible if your device bootloops. Please back up your data before using!

Expand All @@ -32,17 +32,22 @@ Note: I am not responsible if your device bootloops. Please back up your data be
- AI Enabler
- Springboard Options (from Cowabunga Lite)
- Internal Options (from Cowabunga Lite)
- Risky (Hidden) Options:
- OTA Killer
- Custom Resolution

## Running the Program
**Requirements:**
- pymobiledevice3
- Python 3.8 or newer

- **Windows:**
- Either [Apple Devices (from Microsoft Store)](https://apps.microsoft.com/detail/9np83lwlpz9k%3Fhl%3Den-US%26gl%3DUS&ved=2ahUKEwjE-svo7qyJAxWTlYkEHQpbH3oQFnoECBoQAQ&usg=AOvVaw0rZTXCFmRaHAifkEEu9tMI) app or [iTunes (from Apple website)](https://support.apple.com/en-us/106372)
- **Linux:**
- [usbmuxd](https://github.com/libimobiledevice/usbmuxd) and [libimobiledevice](https://github.com/libimobiledevice/libimobiledevice)

- **For Running Python:**
- pymobiledevice3
- PySide6
- Python 3.8 or newer

Note: It is highly recommended to use a virtual environment:
```
python3 -m venv .env # only needed once
Expand Down Expand Up @@ -73,7 +78,8 @@ The application itself can be compiled by running `compile.py`.

## Credits
- [JJTech](https://github.com/JJTech0130) for Sparserestore/[TrollRestore](https://github.com/JJTech0130/TrollRestore)
- [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)
- [disfordottie](https://x.com/disfordottie) for some global flag features
- [sneakyf1shy](https://github.com/f1shy-dev) for [AI Eligibility](https://gist.github.com/f1shy-dev/23b4a78dc283edd30ae2b2e6429129b5) (iOS 18.1 beta 4 and below)

- [lrdsnow](https://github.com/Lrdsnow) for [EU Enabler](https://github.com/Lrdsnow/EUEnabler)
- [pymobiledevice3](https://github.com/doronz88/pymobiledevice3) for restoring and device algorithms.
- [PySide6](https://doc.qt.io/qtforpython-6/) for the GUI library.
3 changes: 2 additions & 1 deletion compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
'--onedir',
'--noconfirm',
'--name=Nugget',
'--icon=nugget.ico'
'--icon=nugget.ico',
'--optimize=2'
]

if platform == "darwin":
Expand Down
8 changes: 8 additions & 0 deletions devicemanagement/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ def __init__(self, uuid: int, name: str, version: str, build: str, model: str, h
self.locale = locale
self.ld = ld

def is_exploit_fully_patched(self) -> bool:
parsed_ver: Version = Version(self.version)
# mobile gestalt methods are completely patched on iOS 18.2 dev beta 3+
if (parsed_ver < Version("18.2")
or self.build == "22C5109p" or self.build == "22C5125e"):
return False
return True

def has_exploit(self) -> bool:
parsed_ver: Version = Version(self.version)
# make sure versions past 17.7.1 but before 18.0 aren't supported
Expand Down
112 changes: 69 additions & 43 deletions devicemanagement/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,37 @@
from devicemanagement.constants import Device, Version
from devicemanagement.data_singleton import DataSingleton

from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, RdarFixTweak
from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, AdvancedPlistTweak, RdarFixTweak
from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks
from tweaks.basic_plist_locations import FileLocationsList
from tweaks.basic_plist_locations import FileLocationsList, RiskyFileLocationsList
from Sparserestore.restore import restore_files, FileToRestore

def show_error_msg(txt: str):
def show_error_msg(txt: str, detailed_txt: str = None):
detailsBox = QMessageBox()
detailsBox.setIcon(QMessageBox.Critical)
detailsBox.setWindowTitle("Error!")
detailsBox.setText(txt)
detailsBox.setDetailedText(str(traceback.format_exc()))
if detailed_txt != None:
detailsBox.setDetailedText(detailed_txt)
detailsBox.exec()

def show_apply_error(e: Exception, update_label=lambda x: None):
if "Find My" in str(e):
show_error_msg("Find My must be disabled in order to use this tool.",
detailed_txt="Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.")
elif "Encrypted Backup MDM" in str(e):
show_error_msg("Nugget cannot be used on this device. Click Show Details for more info.",
detailed_txt="Your device is managed and MDM backup encryption is on. This must be turned off in order for Nugget to work. Please do not use Nugget on your school/work device!")
elif "SessionInactive" in str(e):
show_error_msg("The session was terminated. Refresh the device list and try again.")
elif "Password" in str(e):
show_error_msg("Device is password protected! You must trust the computer on your device.",
detailed_txt="Unlock your device. On the popup, click \"Trust\", enter your password, then try again.")
else:
show_error_msg(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc()))
print(traceback.format_exc())
update_label("Failed to restore")

class DeviceManager:
## Class Functions
def __init__(self):
Expand All @@ -33,8 +51,9 @@ def __init__(self):

# preferences
self.apply_over_wifi = True
self.skip_setup = True
self.auto_reboot = True
self.allow_risky_tweaks = False
self.skip_setup = True
self.supervised = False
self.organization_name = ""

Expand All @@ -50,7 +69,7 @@ def get_devices(self, settings: QSettings):
If you are on Windows, make sure you have the \"Apple Devices\" app from the Microsoft Store or iTunes from Apple's website.
If you are on Linux, make sure you have usbmuxd and libimobiledevice installed.
"""
""", detailed_txt=str(traceback.format_exc())
)
self.set_current_device(index=None)
return
Expand Down Expand Up @@ -99,7 +118,7 @@ def get_devices(self, settings: QSettings):
self.devices.append(dev)
except Exception as e:
print(f"ERROR with lockdown device with UUID {device.serial}")
show_error_msg(type(e).__name__ + ": " + repr(e))
show_error_msg(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc()))
connected_devices.remove(device)
else:
connected_devices.remove(device)
Expand Down Expand Up @@ -155,11 +174,23 @@ def get_current_device_uuid(self) -> str:
else:
return self.data_singleton.current_device.uuid

def get_current_device_model(self) -> str:
if self.data_singleton.current_device == None:
return ""
else:
return self.data_singleton.current_device.model

def get_current_device_supported(self) -> bool:
if self.data_singleton.current_device == None:
return False
else:
return self.data_singleton.current_device.supported()

def get_current_device_patched(self) -> bool:
if self.data_singleton.current_device == None:
return True
else:
return self.data_singleton.current_device.is_exploit_fully_patched()


def reset_device_pairing(self):
Expand Down Expand Up @@ -203,21 +234,33 @@ def add_skip_setup(self, files_to_restore: list[FileToRestore]):
domain="ManagedPreferencesDomain"
))

def get_domain_for_path(self, path: str) -> str:
def get_domain_for_path(self, path: str, fully_patched: bool = False) -> str:
# just make the Sys Containers to use the regular way (won't work for mga)
sysSharedContainer = "SysSharedContainerDomain-"
sysContainer = "SysContainerDomain-"
if not fully_patched:
sysSharedContainer += "."
sysContainer += "."
mappings: dict = {
"/var/Managed Preferences/": "ManagedPreferencesDomain",
"/var/root/": "RootDomain",
"/var/preferences/": "SystemPreferencesDomain",
"/var/MobileDevice/": "MobileDeviceDomain",
"/var/mobile/": "HomeDomain",
"/var/db/": "DatabaseDomain",
"/var/containers/Shared/SystemGroup/": "SysSharedContainerDomain-.",
"/var/containers/Data/SystemGroup/": "SysContainerDomain-."
"/var/containers/Shared/SystemGroup/": sysSharedContainer,
"/var/containers/Data/SystemGroup/": sysContainer
}
for mapping in mappings.keys():
if path.startswith(mapping):
new_path = path.replace(mapping, "")
return mappings[mapping], new_path
new_domain = mappings[mapping]
# if patched, include the next part of the path in the domain
if fully_patched and (new_domain == sysSharedContainer or new_domain == sysContainer):
parts = new_path.split("/")
new_domain += parts[0]
new_path = new_path.replace(parts[0] + "/", "")
return new_domain, new_path
return None, path

def concat_file(self, contents: str, path: str, files_to_restore: list[FileToRestore]):
Expand All @@ -227,7 +270,7 @@ def concat_file(self, contents: str, path: str, files_to_restore: list[FileToRes
restore_path=path
))
else:
domain, file_path = self.get_domain_for_path(path)
domain, file_path = self.get_domain_for_path(path, fully_patched=self.get_current_device_patched())
files_to_restore.append(FileToRestore(
contents=contents,
restore_path=file_path,
Expand Down Expand Up @@ -259,8 +302,8 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
eligibility_files = tweak.apply_tweak()
elif isinstance(tweak, AITweak):
ai_file = tweak.apply_tweak()
elif isinstance(tweak, BasicPlistTweak) or isinstance(tweak, RdarFixTweak):
basic_plists = tweak.apply_tweak(basic_plists)
elif isinstance(tweak, BasicPlistTweak) or isinstance(tweak, RdarFixTweak) or isinstance(tweak, AdvancedPlistTweak):
basic_plists = tweak.apply_tweak(basic_plists, self.allow_risky_tweaks)
else:
if gestalt_plist != None:
gestalt_plist = tweak.apply_tweak(gestalt_plist)
Expand Down Expand Up @@ -325,6 +368,13 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
path=location.value,
files_to_restore=files_to_restore
)
if self.allow_risky_tweaks:
for location in RiskyFileLocationsList:
self.concat_file(
contents=empty_data,
path=location.value,
files_to_restore=files_to_restore
)

# restore to the device
update_label("Restoring to device...")
Expand All @@ -336,23 +386,7 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
QMessageBox.information(None, "Success!", "All done! " + msg)
update_label("Success!")
except Exception as e:
if "Find My" in str(e):
detailsBox = QMessageBox()
detailsBox.setIcon(QMessageBox.Critical)
detailsBox.setWindowTitle("Error!")
detailsBox.setText("Find My must be disabled in order to use this tool.")
detailsBox.setDetailedText("Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.")
detailsBox.exec()
elif "SessionInactive" in str(e):
detailsBox = QMessageBox()
detailsBox.setIcon(QMessageBox.Critical)
detailsBox.setWindowTitle("Error!")
detailsBox.setText("The session was terminated. Refresh the device list and try again.")
detailsBox.exec()
else:
print(traceback.format_exc())
update_label("Failed to restore")
show_error_msg(type(e).__name__ + ": " + repr(e))
show_apply_error(e, update_label)

## RESETTING MOBILE GESTALT
def reset_mobilegestalt(self, settings: QSettings, update_label=lambda x: None):
Expand All @@ -363,7 +397,9 @@ def reset_mobilegestalt(self, settings: QSettings, update_label=lambda x: None):
settings.setValue(self.data_singleton.current_device.uuid + "_model", "")
settings.setValue(self.data_singleton.current_device.uuid + "_hardware", "")
settings.setValue(self.data_singleton.current_device.uuid + "_cpu", "")
domain, file_path = self.get_domain_for_path("/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist")
domain, file_path = self.get_domain_for_path(
"/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist",
fully_patched=self.get_current_device_patched())
restore_files(files=[FileToRestore(
contents=b"",
restore_path=file_path,
Expand All @@ -375,14 +411,4 @@ def reset_mobilegestalt(self, settings: QSettings, update_label=lambda x: None):
QMessageBox.information(None, "Success!", "All done! " + msg)
update_label("Success!")
except Exception as e:
if "Find My" in str(e):
detailsBox = QMessageBox()
detailsBox.setIcon(QMessageBox.Critical)
detailsBox.setWindowTitle("Error!")
detailsBox.setText("Find My must be disabled in order to use this tool.")
detailsBox.setDetailedText("Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.")
detailsBox.exec()
else:
print(traceback.format_exc())
update_label("Failed to restore")
show_error_msg(type(e).__name__ + ": " + repr(e))
show_apply_error(e)
Loading

0 comments on commit c92de7a

Please sign in to comment.