Skip to content
This repository has been archived by the owner on Jan 20, 2025. It is now read-only.

Commit

Permalink
Merge pull request #271 from leminlimez/v4.2
Browse files Browse the repository at this point in the history
v4.2
  • Loading branch information
leminlimez authored Dec 11, 2024
2 parents c85b60d + 843d536 commit 26f0e45
Show file tree
Hide file tree
Showing 17 changed files with 2,683 additions and 1,050 deletions.
51 changes: 41 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,68 @@ 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.**

Make sure you have installed the [requirements](#requirements) if you are on Windows or Linux.

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!

## Features
### iOS 17.0+
- Enable Dynamic Island on any device
- Enable iPhone X gestures on iPhone SEs
- Change Device Model Name (ie what shows in the Settings app)
- Enable Boot Chime
- Enable Charge Limit
- Enable Tap to Wake on unsupported devices (ie iPhone SEs)
- Enable iPhone 16 Settings
- Enable Collision SOS
- Enable Stage Manager
- Disable the Wallpaper Parallax
- Disable Region Restrictions (ie. Shutter Sound)
- Note: This does not include enabling EU sideloading outside the EU. That will come later.
- Enable AOD on any device
- Show the Apple Pencil options in Settings app
- Show the Action Button options in Settings app
- Show Internal Storage info (Might cause problems on some devices, use at your own risk)
- Enabling lock screen clock animation, lock screen page duplication button, and more!
- Disabling the new iOS 18 Photos UI
- EU Enabler
- AI Enabler
- Springboard Options (from Cowabunga Lite)
- Internal Options (from Cowabunga Lite)
- EU Enabler (iOS 17.6-)
- Springboard Options (from [Cowabunga Lite](https://github.com/leminlimez/CowabungaLite))
- Set Lock Screen Footnote
- Disable Lock After Respring
- Disable Screen Dimming While Charging
- Disable Low Battery Alerts
- Internal Options (from [Cowabunga Lite](https://github.com/leminlimez/CowabungaLite))
- Build Version in Status Bar
- Force Right to Left
- Force Metal HUD Debug
- iMessage Diagnostics
- IDS Diagnostics
- VC Diagnostics
- App Store Debug Gesture
- Notes App Debug Mode
- Disable Daemons:
- OTAd
- UsageTrackingAgent
- Game Center
- Screen Time Agent
- Logs, Dumps, and Crash Reports
- ATWAKEUP
- Tipsd
- VPN
- Chinese WLAN service
- HealthKit
- Risky (Hidden) Options:
- Disable thermalmonitord
- OTA Killer
- Custom Resolution
### iOS 18.0+
- Enable iPhone 16 camera button page in the Settings app
- Enable AOD & AOD Vibrancy on any device
- Feature Flags (iOS 18.1b4-):
- Enabling lock screen clock animation, lock screen page duplication button, and more!
- Disabling the new iOS 18 Photos UI (iOS 18.0 betas only, unknown which patched it)
### iOS 18.1+
- AI Enabler + Device Spoofing (fixed in iOS 18.2db3)

## Running the Program
**Requirements:**
## Requirements:
- **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:**
Expand All @@ -48,6 +77,7 @@ Note: I am not responsible if your device bootloops. Please back up your data be
- PySide6
- Python 3.8 or newer

## Running the Python Program
Note: It is highly recommended to use a virtual environment:
```
python3 -m venv .env # only needed once
Expand Down Expand Up @@ -82,6 +112,7 @@ If you would like to read more about the inner workings of the exploit and iOS r
## Credits
- [JJTech](https://github.com/JJTech0130) for Sparserestore/[TrollRestore](https://github.com/JJTech0130/TrollRestore)
- [disfordottie](https://x.com/disfordottie) for some global flag features
- [Mikasa-san](https://github.com/Mikasa-san) for [Quiet Daemon](https://github.com/Mikasa-san/QuietDaemon)
- [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.
Expand Down
4 changes: 2 additions & 2 deletions Sparserestore/restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ def concat_exploit_file(file: FileToRestore, files_list: list[FileToRestore], la
if last_domain != domain_path:
files_list.append(backup.Directory(
"",
f"{domain_path}/",
f"{domain_path}",
owner=file.owner,
group=file.group
))
new_last_domain = domain_path
files_list.append(backup.ConcreteFile(
"",
f"{domain_path}/{name}",
f"{domain_path}{name}",
owner=file.owner,
group=file.group,
contents=file.contents
Expand Down
40 changes: 40 additions & 0 deletions controllers/web_request_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from requests import get, RequestException
from json import JSONDecodeError
from devicemanagement.constants import Version

Nugget_Repo = "leminlimez/Nugget/releases/latest"

last_fetched_version: str = None

def is_update_available(version: str, build: int) -> bool:
# check github for if version < tag (or == tag but build > 0)
latest_version = get_latest_version()
if latest_version != None:
if build > 0 and latest_version == version: # on beta version when there is a public release
return True
elif Version(latest_version) > Version(version):
return True
return False

def get_latest_version() -> str:
global last_fetched_version
# get the cached version
if last_fetched_version != None:
return last_fetched_version
# fetch with web requests
try:
response = get(f"https://api.github.com/repos/{Nugget_Repo}")
response.raise_for_status() # To raise an exception for 4xx/5xx responses

data = response.json() # Parse the JSON response

# Check if "tag_name" exists in the response and compare the version
tag_name = data.get("tag_name")
if tag_name:
last_fetched_version = tag_name.replace("v", "") # Remove 'v' from tag_name
return last_fetched_version
except RequestException as e:
print(f"Error fetching data: {e}")
except JSONDecodeError as e:
print(f"Error parsing JSON: {e}")
return None
89 changes: 57 additions & 32 deletions devicemanagement/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

from pymobiledevice3 import usbmux
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.exceptions import MuxException, PasswordRequiredError

from devicemanagement.constants import Device, Version
from devicemanagement.data_singleton import DataSingleton

from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, AdvancedPlistTweak, RdarFixTweak
from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, AdvancedPlistTweak, RdarFixTweak, NullifyFileTweak
from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks
from tweaks.basic_plist_locations import FileLocationsList, RiskyFileLocationsList
from Sparserestore.restore import restore_files, FileToRestore
Expand All @@ -34,7 +35,7 @@ def show_apply_error(e: Exception, update_label=lambda x: None):
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):
elif isinstance(e, PasswordRequiredError):
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:
Expand All @@ -50,9 +51,11 @@ def __init__(self):
self.current_device_index = 0

# preferences
self.apply_over_wifi = True
# TODO: Move to its own class
self.apply_over_wifi = False
self.auto_reboot = True
self.allow_risky_tweaks = False
self.show_all_spoofable_models = False
self.skip_setup = True
self.supervised = False
self.organization_name = ""
Expand Down Expand Up @@ -116,14 +119,16 @@ def get_devices(self, settings: QSettings):
)
tweaks["RdarFix"].get_rdar_mode(model)
self.devices.append(dev)
except MuxException as e:
# there is probably a cable issue
print(f"MUX ERROR with lockdown device with UUID {device.serial}")
show_error_msg("MuxException: " + repr(e) + "\n\nIf you keep receiving this error, try using a different cable or port.",
detailed_txt=str(traceback.format_exc()))
except Exception as e:
print(f"ERROR with lockdown device with UUID {device.serial}")
show_error_msg(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc()))
connected_devices.remove(device)
else:
connected_devices.remove(device)

if len(connected_devices) > 0:
if len(self.devices) > 0:
self.set_current_device(index=0)
else:
self.set_current_device(index=None)
Expand Down Expand Up @@ -203,8 +208,8 @@ def reset_device_pairing(self):
QMessageBox.information(None, "Pairing Reset", "Your device's pairing was successfully reset. Refresh the device list before applying.")


def add_skip_setup(self, files_to_restore: list[FileToRestore]):
if self.skip_setup and not self.get_current_device_supported():
def add_skip_setup(self, files_to_restore: list[FileToRestore], restoring_domains: bool):
if self.skip_setup and (not self.get_current_device_supported() or restoring_domains):
# add the 2 skip setup files
cloud_config_plist: dict = {
"SkipSetup": ["WiFi", "Location", "Restore", "SIMSetup", "Android", "AppleID", "IntendedUser", "TOS", "Siri", "ScreenTime", "Diagnostics", "SoftwareUpdate", "Passcode", "Biometric", "Payment", "Zoom", "DisplayTone", "MessagingActivationUsingPhoneNumber", "HomeButtonSensitivity", "CloudStorage", "ScreenSaver", "TapToSetup", "Keyboard", "PreferredLanguage", "SpokenLanguage", "WatchMigration", "OnBoarding", "TVProviderSignIn", "TVHomeScreenSync", "Privacy", "TVRoom", "iMessageAndFaceTime", "AppStore", "Safety", "Multitasking", "ActionButton", "TermsOfAddress", "AccessibilityAppearance", "Welcome", "Appearance", "RestoreCompleted", "UpdateCompleted"],
Expand All @@ -220,8 +225,8 @@ def add_skip_setup(self, files_to_restore: list[FileToRestore]):
cloud_config_plist["OrganizationName"] = self.organization_name
files_to_restore.append(FileToRestore(
contents=plistlib.dumps(cloud_config_plist),
restore_path="systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/CloudConfigurationDetails.plist",
domain="SysSharedContainerDomain-."
restore_path="Library/ConfigurationProfiles/CloudConfigurationDetails.plist",
domain="SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles"
))
purplebuddy_plist: dict = {
"SetupDone": True,
Expand All @@ -234,7 +239,12 @@ def add_skip_setup(self, files_to_restore: list[FileToRestore]):
domain="ManagedPreferencesDomain"
))

def get_domain_for_path(self, path: str, fully_patched: bool = False) -> str:
def get_domain_for_path(self, path: str, owner: int = 501) -> str:
# returns Domain: str?, Path: str
if self.get_current_device_supported() and not path.startswith("/var/mobile/") and not owner == 0:
# don't do anything on sparserestore versions
return path, None
fully_patched = self.get_current_device_patched()
# just make the Sys Containers to use the regular way (won't work for mga)
sysSharedContainer = "SysSharedContainerDomain-"
sysContainer = "SysContainerDomain-"
Expand All @@ -260,22 +270,18 @@ def get_domain_for_path(self, path: str, fully_patched: bool = False) -> str:
parts = new_path.split("/")
new_domain += parts[0]
new_path = new_path.replace(parts[0] + "/", "")
return new_domain, new_path
return None, path
return new_path, new_domain
return path, None

def concat_file(self, contents: str, path: str, files_to_restore: list[FileToRestore]):
if self.get_current_device_supported():
files_to_restore.append(FileToRestore(
contents=contents,
restore_path=path
))
else:
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,
domain=domain
))
def concat_file(self, contents: str, path: str, files_to_restore: list[FileToRestore], owner: int = 501, group: int = 501):
# TODO: try using inodes here instead
file_path, domain = self.get_domain_for_path(path, owner=owner)
files_to_restore.append(FileToRestore(
contents=contents,
restore_path=file_path,
domain=domain,
owner=owner, group=group
))

## APPLYING OR REMOVING TWEAKS AND RESTORING
def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
Expand All @@ -291,6 +297,9 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
eligibility_files = None
ai_file = None
basic_plists: dict = {}
basic_plists_ownership: dict = {}
files_data: dict = {}
uses_domains: bool = False

# set the plist keys
if not resetting:
Expand All @@ -304,6 +313,13 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
ai_file = tweak.apply_tweak()
elif isinstance(tweak, BasicPlistTweak) or isinstance(tweak, RdarFixTweak) or isinstance(tweak, AdvancedPlistTweak):
basic_plists = tweak.apply_tweak(basic_plists, self.allow_risky_tweaks)
basic_plists_ownership[tweak.file_location] = tweak.owner
if tweak.owner == 0:
uses_domains = True
elif isinstance(tweak, NullifyFileTweak):
tweak.apply_tweak(files_data)
if tweak.enabled and tweak.file_location.value.startswith("/var/mobile/"):
uses_domains = True
else:
if gestalt_plist != None:
gestalt_plist = tweak.apply_tweak(gestalt_plist)
Expand All @@ -327,7 +343,7 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
path="/var/preferences/FeatureFlags/Global.plist",
files_to_restore=files_to_restore
)
self.add_skip_setup(files_to_restore)
self.add_skip_setup(files_to_restore, uses_domains)
if gestalt_data != None:
self.concat_file(
contents=gestalt_data,
Expand All @@ -354,10 +370,19 @@ def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
files_to_restore=files_to_restore
)
for location, plist in basic_plists.items():
ownership = basic_plists_ownership[location]
self.concat_file(
contents=plistlib.dumps(plist),
path=location.value,
files_to_restore=files_to_restore
files_to_restore=files_to_restore,
owner=ownership, group=ownership
)
for location, data in files_data.items():
self.concat_file(
contents=data,
path=location.value,
files_to_restore=files_to_restore,
owner=ownership, group=ownership
)
# reset basic tweaks
if resetting:
Expand Down Expand Up @@ -397,9 +422,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",
fully_patched=self.get_current_device_patched())
file_path, domain = self.get_domain_for_path(
"/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist"
)
restore_files(files=[FileToRestore(
contents=b"",
restore_path=file_path,
Expand Down
Loading

0 comments on commit 26f0e45

Please sign in to comment.