├── 2.py ├── AIEnabler.py ├── README.md ├── backend ├── __pycache__ │ ├── device_manager.cpython-312.pyc │ ├── device_manager.cpython-39.pyc │ └── funcs.cpython-312.pyc ├── device_manager.py ├── funcs.py └── mainwindow.py ├── exploit ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── backup.cpython-312.pyc │ ├── mbdb.cpython-312.pyc │ └── restore.cpython-312.pyc ├── backup.py ├── mbdb.py └── restore.py └── tweak ├── __pycache__ └── eligibility.cpython-312.pyc ├── eligibility.py └── files ├── eligibility.plist └── restore.json /2.py: -------------------------------------------------------------------------------- 1 | from backend.device_manager import DeviceManager 2 | from backend.funcs import prompt 3 | from pymobiledevice3 import usbmux 4 | from tweak.eligibility import EUTweak 5 | 6 | print("Initializing...") 7 | 8 | prompt_options = [ 9 | "1. Restore files with no data", 10 | "2. Apply eligibility and config patches", 11 | "3. Restore files with no data and apply patches", 12 | "4. Automated eligibility and config patch spam" 13 | ] 14 | 15 | dev_manager = DeviceManager() 16 | 17 | tweak = EUTweak() 18 | tweak.apply(dev_manager) -------------------------------------------------------------------------------- /AIEnabler.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tkinter as tk 3 | from tkinter import filedialog 4 | from lxml import etree 5 | 6 | def action_prompt(): 7 | print("Select an option:") 8 | print("1. Enable AI") 9 | print("2. Undo AI") 10 | choice = input("Enter 1 or 2: ").strip() 11 | 12 | if choice not in ['1', '2']: 13 | print("Invalid input. Please enter 1 or 2.") 14 | return action_prompt() 15 | 16 | return choice 17 | 18 | def select_file(): 19 | root = tk.Tk() 20 | root.withdraw() # Hide the main tkinter window 21 | 22 | file_path = filedialog.askopenfilename( 23 | title="Select a .plist file", 24 | filetypes=(("plist files", "*.plist"), ("all files", "*.*")) 25 | ) 26 | 27 | if not file_path: 28 | print("No file selected. Exiting.") 29 | exit() 30 | 31 | return file_path 32 | 33 | def modify_plist(file_path, action_choice): 34 | script_dir = os.path.dirname(os.path.abspath(__file__)) 35 | destination_folder = os.path.join(script_dir, 'tweak', 'files') 36 | original_file_path = os.path.join(destination_folder, 'plistbackup') 37 | normal_file_path = os.path.join(destination_folder, 'com.apple.MobileGestalt.plist') 38 | noai_file_path = os.path.join(destination_folder, 'noai.plist') 39 | 40 | if not os.path.exists(destination_folder): 41 | os.makedirs(destination_folder) 42 | 43 | if action_choice == '1': 44 | # Enable AI 45 | if os.path.exists(normal_file_path): 46 | os.rename(normal_file_path, original_file_path) 47 | print(f"Original file backed up as '{original_file_path}'") 48 | 49 | # Load and modify plist 50 | tree = etree.parse(file_path) 51 | root = tree.getroot() 52 | cache_extra_found = False 53 | key_found = False 54 | 55 | # Namespaces are used in plist files, so we need to handle them 56 | ns = {'': 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'} 57 | 58 | # Iterate through the XML elements to find and modify 59 | for elem in root.iter(): 60 | if elem.tag == 'key' and elem.text == 'CacheExtra': 61 | cache_extra_found = True 62 | # Create and insert the new key-value pair 63 | for next_elem in elem.itersiblings(): 64 | if next_elem.tag == 'dict': 65 | # Add new keys and values 66 | new_key = etree.Element('key') 67 | new_key.text = 'A62OafQ85EJAiiqKn4agtg' 68 | new_value = etree.Element('integer') 69 | new_value.text = '1' 70 | next_elem.append(new_key) 71 | next_elem.append(new_value) 72 | print("Added A62OafQ85EJAiiqKn4agtg with value 1 under CacheExtra") 73 | break 74 | 75 | if elem.tag == 'key' and elem.text == 'h9jDsbgj7xIVeIQ8S3/X3Q': 76 | key_found = True 77 | next_elem = elem.getnext() 78 | if next_elem is not None and next_elem.tag == 'string': 79 | next_elem.text = 'iPhone16,2' 80 | print("Updated h9jDsbgj7xIVeIQ8S3/X3Q to 'iPhone16,2'") 81 | 82 | if not cache_extra_found: 83 | # If 'CacheExtra' is not found, add it with the required elements 84 | cache_extra_elem = etree.Element('key') 85 | cache_extra_elem.text = 'CacheExtra' 86 | dict_elem = etree.Element('dict') 87 | cache_extra_elem.append(dict_elem) 88 | root.append(cache_extra_elem) 89 | 90 | # Add required key-value pairs 91 | new_key = etree.Element('key') 92 | new_key.text = 'A62OafQ85EJAiiqKn4agtg' 93 | new_value = etree.Element('integer') 94 | new_value.text = '1' 95 | dict_elem.append(new_key) 96 | dict_elem.append(new_value) 97 | print("Added CacheExtra with A62OafQ85EJAiiqKn4agtg and 1") 98 | 99 | if not key_found: 100 | # Add the 'h9jDsbgj7xIVeIQ8S3/X3Q' key if not found 101 | new_key = etree.Element('key') 102 | new_key.text = 'h9jDsbgj7xIVeIQ8S3/X3Q' 103 | new_value = etree.Element('string') 104 | new_value.text = 'iPhone16,2' 105 | root.append(new_key) 106 | root.append(new_value) 107 | print("Added h9jDsbgj7xIVeIQ8S3/X3Q with value 'iPhone16,2'") 108 | 109 | tree.write(file_path, xml_declaration=True, encoding='utf-8') 110 | 111 | # Rename modified file and copy it 112 | os.rename(file_path, normal_file_path) 113 | print(f"File copied and renamed to '{normal_file_path}'") 114 | 115 | # Create a copy named noai.plist 116 | with open(normal_file_path, 'r', encoding='utf-8') as file: 117 | content = file.read() 118 | 119 | with open(noai_file_path, 'w', encoding='utf-8') as file: 120 | file.write(content) 121 | print(f"Modified file saved as '{noai_file_path}'") 122 | 123 | elif action_choice == '2': 124 | # Undo AI 125 | if os.path.exists(normal_file_path): 126 | os.remove(normal_file_path) 127 | print(f"Deleted existing file '{normal_file_path}'") 128 | 129 | if os.path.exists(noai_file_path): 130 | os.rename(noai_file_path, normal_file_path) 131 | print(f"Replaced with 'noai.plist' as '{normal_file_path}'") 132 | else: 133 | print(f"'noai.plist' not found in '{destination_folder}'. Prompting for file selection.") 134 | 135 | # Prompt for file selection if 'noai.plist' doesn't exist 136 | plist_file = select_file() 137 | if plist_file: 138 | # Use the selected file to undo AI 139 | os.rename(plist_file, noai_file_path) 140 | print(f"Selected file '{plist_file}' saved as '{noai_file_path}'") 141 | os.rename(noai_file_path, normal_file_path) 142 | print(f"Replaced with selected file as '{normal_file_path}'") 143 | else: 144 | print("No file selected. Exiting.") 145 | 146 | def main(): 147 | action_choice = action_prompt() 148 | 149 | plist_file = None 150 | if action_choice == '1': 151 | plist_file = select_file() 152 | print("Select your mobilegestalt file") 153 | 154 | modify_plist(plist_file, action_choice) 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | How to use: 2 | open cmd and cd to the path where you extracted the files to. run pip install pymobiledevice3 tk lxml then run python aienabler.py, select your mobilegestalt file and tell it if your on a ipad or iphone. then it will patch ur mobilegest then run 2.py with your device plugged in and boom, 3 | https://discord.gg/drfshayK5J 4 | 5 | Disclaimer 6 | This is a proof of concept and not a "hack" for apple devices, apple dont sue me 7 | -------------------------------------------------------------------------------- /backend/__pycache__/device_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/backend/__pycache__/device_manager.cpython-312.pyc -------------------------------------------------------------------------------- /backend/__pycache__/device_manager.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/backend/__pycache__/device_manager.cpython-39.pyc -------------------------------------------------------------------------------- /backend/__pycache__/funcs.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/backend/__pycache__/funcs.cpython-312.pyc -------------------------------------------------------------------------------- /backend/device_manager.py: -------------------------------------------------------------------------------- 1 | from pymobiledevice3 import usbmux 2 | from pymobiledevice3.lockdown import create_using_usbmux 3 | 4 | class DeviceManager: 5 | def __init__(self): 6 | self.devices = [] 7 | self.get_devices() 8 | if len(self.devices) != 0: 9 | self.set_device(0) 10 | 11 | def get_devices(self): 12 | self.devices.clear() 13 | connected_devices = usbmux.list_devices() 14 | uuids = set() # added to avoid devices getting added multiple times 15 | 16 | for device in connected_devices: 17 | if device.serial in uuids: 18 | continue 19 | 20 | try: 21 | ld = create_using_usbmux(serial=device.serial) 22 | vals = ld.all_values 23 | dev = { 24 | "uuid": device.serial, 25 | "name": vals['DeviceName'], 26 | "version": vals['ProductVersion'], 27 | "model": vals['ProductType'], 28 | "locale": ld.locale, 29 | "ld": ld 30 | } 31 | self.devices.append(dev) 32 | uuids.add(device.serial) 33 | except Exception as e: 34 | print(f"ERROR with lockdown device with UUID {device.serial}") 35 | 36 | if len(self.devices) == 0: 37 | print("No devices found!") 38 | else: 39 | print(f"{len(self.devices)} device(s) found!") 40 | 41 | def set_device(self, device): 42 | if device != None: 43 | self.device = self.devices[device] 44 | else: 45 | self.device = None -------------------------------------------------------------------------------- /backend/funcs.py: -------------------------------------------------------------------------------- 1 | from exploit.restore import FileToRestore 2 | 3 | def prompt(options): 4 | print("Select an option:") 5 | print("\n".join(options)) 6 | choice = input("Enter your choice: ") 7 | return choice 8 | 9 | def retrieve_restore_files(eligibility_data, config_data): 10 | files = [ 11 | FileToRestore(contents=eligibility_data, restore_path="/var/db/os_eligibility/", restore_name="eligibility.plist"), 12 | FileToRestore(contents=config_data, restore_path="/var/MobileAsset/AssetsV2/com_apple_MobileAsset_OSEligibility/purpose_auto/c55a421c053e10233e5bfc15c42fa6230e5639a9.asset/AssetData/", restore_name="Config.plist") 13 | ] 14 | return files 15 | -------------------------------------------------------------------------------- /backend/mainwindow.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 2 | QMetaObject, QObject, QPoint, QRect, 3 | QSize, QTime, QUrl, Qt) 4 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 5 | QFont, QFontDatabase, QGradient, QIcon, 6 | QImage, QKeySequence, QLinearGradient, QPainter, 7 | QPalette, QPixmap, QRadialGradient, QTransform) 8 | from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFrame, 9 | QHBoxLayout, QLabel, QLineEdit, QMainWindow, 10 | QProgressBar, QScrollArea, QSizePolicy, QSpacerItem, 11 | QStackedWidget, QToolButton, QVBoxLayout, QWidget) 12 | import webbrowser 13 | 14 | class MainWindow(QMainWindow): 15 | def __init__(self, deviceManager, tweak): 16 | super().__init__() 17 | 18 | self.dev_manager = deviceManager 19 | self.tweak = tweak 20 | self.setWindowTitle("EUEnabler") 21 | self.setFixedSize(960, 540) 22 | self.setWindowIcon(QIcon("icon.png")) 23 | screen_geometry = QApplication.primaryScreen().availableGeometry() 24 | 25 | x = (screen_geometry.width() - self.width()) // 2 26 | y = (screen_geometry.height() - self.height()) // 2 27 | 28 | self.move(x, y) 29 | self.setupUI() 30 | 31 | def setupUI(self): 32 | self.main = QWidget(self) 33 | 34 | self.logoBtn = QToolButton(self.main) 35 | self.logoBtn.setObjectName(u"logoBtn") 36 | self.logoBtn.setFixedSize(200, 200) 37 | self.logoBtn.move((self.width() - self.logoBtn.width()) // 2, 50) 38 | self.logoBtn.setStyleSheet(u"QToolButton {\n" 39 | " background-color: transparent;\n" 40 | " padding: 0px;\n" 41 | " border: none;\n" 42 | "}") 43 | 44 | icon = QIcon() 45 | icon.addFile(u"icon.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) 46 | self.logoBtn.setIcon(icon) 47 | self.logoBtn.setIconSize(QSize(400, 400)) 48 | self.logoBtn.clicked.connect(self.logoBtn_callback) 49 | 50 | self.EUText = QLabel("EUEnabler", self) 51 | font = QFont() 52 | font.setBold(True) 53 | font.setPointSize(24) 54 | self.EUText.setFont(font) 55 | self.EUText.setFixedSize(self.width(), 50) 56 | self.EUText.move(0, 245) 57 | self.EUText.setAlignment(Qt.AlignCenter) 58 | 59 | self.deviceComboBox = QComboBox(self) 60 | self.deviceComboBox.setFixedSize(300, 40) 61 | self.deviceComboBox.move((self.width() - self.deviceComboBox.width()) // 2, 295) 62 | self.deviceComboBox.currentIndexChanged.connect(self.deviceChanged) 63 | self.updateDeviceList() 64 | 65 | icon = QIcon() 66 | icon.addFile(u"icon/check-circle.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off) 67 | self.applyBtn = QToolButton(self) 68 | self.applyBtn.setObjectName(u"applyBtn") 69 | self.applyBtn.setFixedSize(36, 36) 70 | self.applyBtn.setStyleSheet(u"QToolButton {\n" 71 | " background-color: transparent;\n" 72 | " padding: 0px;\n" 73 | " border: none;\n" 74 | "}") 75 | self.applyBtn.setIcon(icon) 76 | self.applyBtn.setIconSize(QSize(36, 36)) 77 | self.applyBtn.clicked.connect(self.applyBtn_callback) 78 | self.applyBtn.move((self.width() - self.applyBtn.width()) // 2, 478) 79 | 80 | self.setCentralWidget(self.main) 81 | 82 | def updateDeviceList(self): 83 | self.deviceComboBox.clear() 84 | 85 | for idx, device in enumerate(self.dev_manager.devices): 86 | self.deviceComboBox.addItem(f" {device.get('name')}", idx) 87 | 88 | if self.dev_manager.devices: 89 | self.deviceComboBox.setCurrentIndex(0) 90 | 91 | def deviceChanged(self, index): 92 | selected_device = self.deviceComboBox.itemData(index) 93 | self.dev_manager.set_device(selected_device) 94 | 95 | def logoBtn_callback(self): 96 | webbrowser.open("https://github.com/nightfallenxyz/EUEnabler-ASH") 97 | 98 | def applyBtn_callback(self): 99 | self.tweak.apply() 100 | 101 | def keyPressEvent(self, event): 102 | if event.key() == Qt.Key_R: 103 | self.dev_manager.get_devices() 104 | self.updateDeviceList() -------------------------------------------------------------------------------- /exploit/__init__.py: -------------------------------------------------------------------------------- 1 | from tempfile import TemporaryDirectory 2 | from pathlib import Path 3 | 4 | from pymobiledevice3.lockdown import create_using_usbmux 5 | from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service 6 | from pymobiledevice3.exceptions import PyMobileDevice3Exception 7 | from pymobiledevice3.services.diagnostics import DiagnosticsService 8 | from pymobiledevice3.lockdown import LockdownClient 9 | 10 | from . import backup 11 | 12 | def perform_restore(backup: backup.Backup, reboot: bool = False, lockdown_client: LockdownClient = None): 13 | try: 14 | with TemporaryDirectory() as backup_dir: 15 | backup.write_to_directory(Path(backup_dir)) 16 | 17 | if lockdown_client == None: 18 | lockdown_client = create_using_usbmux() 19 | with Mobilebackup2Service(lockdown_client) as mb: 20 | mb.restore(backup_dir, system=True, reboot=False, copy=False, source=".") 21 | except PyMobileDevice3Exception as e: 22 | if "Find My" in str(e): 23 | print("Find My must be disabled in order to use this tool.") 24 | print("Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.") 25 | raise e 26 | elif "crash_on_purpose" not in str(e): 27 | raise e 28 | else: 29 | if reboot and lockdown_client != None: 30 | print("Success! Rebooting your device...") 31 | with DiagnosticsService(lockdown_client) as diagnostics_service: 32 | diagnostics_service.restart() 33 | print("Remember to turn Find My back on!") -------------------------------------------------------------------------------- /exploit/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/exploit/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /exploit/__pycache__/backup.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/exploit/__pycache__/backup.cpython-312.pyc -------------------------------------------------------------------------------- /exploit/__pycache__/mbdb.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/exploit/__pycache__/mbdb.cpython-312.pyc -------------------------------------------------------------------------------- /exploit/__pycache__/restore.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/exploit/__pycache__/restore.cpython-312.pyc -------------------------------------------------------------------------------- /exploit/backup.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | import plistlib 4 | from pathlib import Path 5 | from base64 import b64decode 6 | from hashlib import sha1 7 | from . import mbdb 8 | from .mbdb import _FileMode 9 | from random import randbytes 10 | from typing import Optional 11 | 12 | # RWX:RX:RX 13 | DEFAULT = _FileMode.S_IRUSR | _FileMode.S_IWUSR | _FileMode.S_IXUSR | _FileMode.S_IRGRP | _FileMode.S_IXGRP | _FileMode.S_IROTH | _FileMode.S_IXOTH 14 | 15 | @dataclass 16 | class BackupFile: 17 | path: str 18 | domain: str 19 | 20 | def to_record(self) -> mbdb.MbdbRecord: 21 | raise NotImplementedError() 22 | 23 | @dataclass 24 | class ConcreteFile(BackupFile): 25 | contents: bytes 26 | owner: int = 0 27 | group: int = 0 28 | inode: Optional[int] = None 29 | mode: _FileMode = DEFAULT 30 | 31 | def to_record(self) -> mbdb.MbdbRecord: 32 | if self.inode is None: 33 | self.inode = int.from_bytes(randbytes(8), "big") 34 | return mbdb.MbdbRecord( 35 | domain=self.domain, 36 | filename=self.path, 37 | link="", 38 | hash=sha1(self.contents).digest(), 39 | key=b"", 40 | mode=self.mode | _FileMode.S_IFREG, 41 | #unknown2=0, 42 | #unknown3=0, 43 | inode=self.inode, 44 | user_id=self.owner, 45 | group_id=self.group, 46 | mtime=int(datetime.now().timestamp()), 47 | atime=int(datetime.now().timestamp()), 48 | ctime=int(datetime.now().timestamp()), 49 | size=len(self.contents), 50 | flags=4, 51 | properties=[] 52 | ) 53 | 54 | @dataclass 55 | class Directory(BackupFile): 56 | owner: int = 0 57 | group: int = 0 58 | mode: _FileMode = DEFAULT 59 | 60 | def to_record(self) -> mbdb.MbdbRecord: 61 | return mbdb.MbdbRecord( 62 | domain=self.domain, 63 | filename=self.path, 64 | link="", 65 | hash=b"", 66 | key=b"", 67 | mode=self.mode | _FileMode.S_IFDIR, 68 | #unknown2=0, 69 | #unknown3=0, 70 | inode=0, # inode is not respected for directories 71 | user_id=self.owner, 72 | group_id=self.group, 73 | mtime=int(datetime.now().timestamp()), 74 | atime=int(datetime.now().timestamp()), 75 | ctime=int(datetime.now().timestamp()), 76 | size=0, 77 | flags=4, 78 | properties=[] 79 | ) 80 | 81 | @dataclass 82 | class SymbolicLink(BackupFile): 83 | target: str 84 | owner: int = 0 85 | group: int = 0 86 | inode: Optional[int] = None 87 | mode: _FileMode = DEFAULT 88 | 89 | def to_record(self) -> mbdb.MbdbRecord: 90 | if self.inode is None: 91 | self.inode = int.from_bytes(randbytes(8), "big") 92 | return mbdb.MbdbRecord( 93 | domain=self.domain, 94 | filename=self.path, 95 | link=self.target, 96 | hash=b"", 97 | key=b"", 98 | mode=self.mode | _FileMode.S_IFLNK, 99 | #unknown2=0, 100 | #unknown3=0, 101 | inode=self.inode, 102 | user_id=self.owner, 103 | group_id=self.group, 104 | mtime=int(datetime.now().timestamp()), 105 | atime=int(datetime.now().timestamp()), 106 | ctime=int(datetime.now().timestamp()), 107 | size=0, 108 | flags=4, 109 | properties=[] 110 | ) 111 | 112 | @dataclass 113 | class Backup: 114 | files: list[BackupFile] 115 | 116 | def write_to_directory(self, directory: Path): 117 | for file in self.files: 118 | if isinstance(file, ConcreteFile): 119 | #print("Writing", file.path, "to", directory / sha1((file.domain + "-" + file.path).encode()).digest().hex()) 120 | with open(directory / sha1((file.domain + "-" + file.path).encode()).digest().hex(), "wb") as f: 121 | f.write(file.contents) 122 | 123 | with open(directory / "Manifest.mbdb", "wb") as f: 124 | f.write(self.generate_manifest_db().to_bytes()) 125 | 126 | with open(directory / "Status.plist", "wb") as f: 127 | f.write(self.generate_status()) 128 | 129 | with open(directory / "Manifest.plist", "wb") as f: 130 | f.write(self.generate_manifest()) 131 | 132 | with open(directory / "Info.plist", "wb") as f: 133 | f.write(plistlib.dumps({})) 134 | 135 | 136 | def generate_manifest_db(self): # Manifest.mbdb 137 | records = [] 138 | for file in self.files: 139 | records.append(file.to_record()) 140 | return mbdb.Mbdb(records=records) 141 | 142 | def generate_status(self) -> bytes: # Status.plist 143 | return plistlib.dumps({ 144 | "BackupState": "new", 145 | "Date": datetime.fromisoformat("1970-01-01T00:00:00+00:00"), 146 | "IsFullBackup": False, 147 | "SnapshotState": "finished", 148 | "UUID": "00000000-0000-0000-0000-000000000000", 149 | "Version": "2.4" 150 | }) 151 | 152 | def generate_manifest(self) -> bytes: # Manifest.plist 153 | return plistlib.dumps({ 154 | "BackupKeyBag": b64decode(""" 155 | VkVSUwAAAAQAAAAFVFlQRQAAAAQAAAABVVVJRAAAABDud41d1b9NBICR1BH9JfVtSE1D 156 | SwAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV1JBUAAA 157 | AAQAAAAAU0FMVAAAABRY5Ne2bthGQ5rf4O3gikep1e6tZUlURVIAAAAEAAAnEFVVSUQA 158 | AAAQB7R8awiGR9aba1UuVahGPENMQVMAAAAEAAAAAVdSQVAAAAAEAAAAAktUWVAAAAAE 159 | AAAAAFdQS1kAAAAoN3kQAJloFg+ukEUY+v5P+dhc/Welw/oucsyS40UBh67ZHef5ZMk9 160 | UVVVSUQAAAAQgd0cg0hSTgaxR3PVUbcEkUNMQVMAAAAEAAAAAldSQVAAAAAEAAAAAktU 161 | WVAAAAAEAAAAAFdQS1kAAAAoMiQTXx0SJlyrGJzdKZQ+SfL124w+2Tf/3d1R2i9yNj9z 162 | ZCHNJhnorVVVSUQAAAAQf7JFQiBOS12JDD7qwKNTSkNMQVMAAAAEAAAAA1dSQVAAAAAE 163 | AAAAAktUWVAAAAAEAAAAAFdQS1kAAAAoSEelorROJA46ZUdwDHhMKiRguQyqHukotrxh 164 | jIfqiZ5ESBXX9txi51VVSUQAAAAQfF0G/837QLq01xH9+66vx0NMQVMAAAAEAAAABFdS 165 | QVAAAAAEAAAAAktUWVAAAAAEAAAAAFdQS1kAAAAol0BvFhd5bu4Hr75XqzNf4g0fMqZA 166 | ie6OxI+x/pgm6Y95XW17N+ZIDVVVSUQAAAAQimkT2dp1QeadMu1KhJKNTUNMQVMAAAAE 167 | AAAABVdSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAo2N2DZarQ6GPoWRgTiy/t 168 | djKArOqTaH0tPSG9KLbIjGTOcLodhx23xFVVSUQAAAAQQV37JVZHQFiKpoNiGmT6+ENM 169 | QVMAAAAEAAAABldSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAofe2QSvDC2cV7 170 | Etk4fSBbgqDx5ne/z1VHwmJ6NdVrTyWi80Sy869DM1VVSUQAAAAQFzkdH+VgSOmTj3yE 171 | cfWmMUNMQVMAAAAEAAAAB1dSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAo7kLY 172 | PQ/DnHBERGpaz37eyntIX/XzovsS0mpHW3SoHvrb9RBgOB+WblVVSUQAAAAQEBpgKOz9 173 | Tni8F9kmSXd0sENMQVMAAAAEAAAACFdSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kA 174 | AAAo5mxVoyNFgPMzphYhm1VG8Fhsin/xX+r6mCd9gByF5SxeolAIT/ICF1VVSUQAAAAQ 175 | rfKB2uPSQtWh82yx6w4BoUNMQVMAAAAEAAAACVdSQVAAAAAEAAAAA0tUWVAAAAAEAAAA 176 | AFdQS1kAAAAo5iayZBwcRa1c1MMx7vh6lOYux3oDI/bdxFCW1WHCQR/Ub1MOv+QaYFVV 177 | SUQAAAAQiLXvK3qvQza/mea5inss/0NMQVMAAAAEAAAACldSQVAAAAAEAAAAA0tUWVAA 178 | AAAEAAAAAFdQS1kAAAAoD2wHX7KriEe1E31z7SQ7/+AVymcpARMYnQgegtZD0Mq2U55u 179 | xwNr2FVVSUQAAAAQ/Q9feZxLS++qSe/a4emRRENMQVMAAAAEAAAAC1dSQVAAAAAEAAAA 180 | A0tUWVAAAAAEAAAAAFdQS1kAAAAocYda2jyYzzSKggRPw/qgh6QPESlkZedgDUKpTr4Z 181 | Z8FDgd7YoALY1g=="""), 182 | "Lockdown": {}, 183 | "SystemDomainsVersion": "20.0", 184 | "Version": "9.1" 185 | }) -------------------------------------------------------------------------------- /exploit/mbdb.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from io import BytesIO 3 | 4 | # Mode bitfield 5 | from enum import IntFlag 6 | class _FileMode(IntFlag): 7 | S_IFMT = 0o0170000 8 | S_IFIFO = 0o0010000 9 | S_IFCHR = 0o0020000 10 | S_IFDIR = 0o0040000 11 | S_IFBLK = 0o0060000 12 | S_IFREG = 0o0100000 13 | S_IFLNK = 0o0120000 14 | S_IFSOCK = 0o0140000 15 | 16 | #S_IRWXU = 0o0000700 17 | S_IRUSR = 0o0000400 18 | S_IWUSR = 0o0000200 19 | S_IXUSR = 0o0000100 20 | 21 | #S_IRWXG = 0o0000070 22 | S_IRGRP = 0o0000040 23 | S_IWGRP = 0o0000020 24 | S_IXGRP = 0o0000010 25 | 26 | #S_IRWXO = 0o0000007 27 | S_IROTH = 0o0000004 28 | S_IWOTH = 0o0000002 29 | S_IXOTH = 0o0000001 30 | 31 | S_ISUID = 0o0004000 32 | S_ISGID = 0o0002000 33 | S_ISVTX = 0o0001000 34 | 35 | @dataclass 36 | class MbdbRecord: 37 | domain: str 38 | filename: str 39 | link: str 40 | hash: bytes 41 | key: bytes 42 | mode: _FileMode 43 | inode: int 44 | user_id: int 45 | group_id: int 46 | mtime: int 47 | atime: int 48 | ctime: int 49 | size: int 50 | flags: int 51 | properties: list 52 | 53 | @classmethod 54 | def from_stream(cls, d: BytesIO): 55 | #d = BytesIO(data) 56 | 57 | domain_len = int.from_bytes(d.read(2), "big") 58 | domain = d.read(domain_len).decode("utf-8") 59 | 60 | filename_len = int.from_bytes(d.read(2), "big") 61 | filename = d.read(filename_len).decode("utf-8") 62 | 63 | link_len = int.from_bytes(d.read(2), "big") 64 | link = d.read(link_len).decode("utf-8") if link_len != 0xffff else "" 65 | 66 | hash_len = int.from_bytes(d.read(2), "big") 67 | hash = d.read(hash_len) if hash_len != 0xffff else b"" 68 | 69 | key_len = int.from_bytes(d.read(2), "big") 70 | key = d.read(key_len) if key_len != 0xffff else b"" 71 | 72 | mode = _FileMode(int.from_bytes(d.read(2), "big")) 73 | #unknown2 = int.from_bytes(d.read(4), "big") 74 | #unknown3 = int.from_bytes(d.read(4), "big") 75 | inode = int.from_bytes(d.read(8), "big") 76 | user_id = int.from_bytes(d.read(4), "big") 77 | group_id = int.from_bytes(d.read(4), "big") 78 | mtime = int.from_bytes(d.read(4), "big") 79 | atime = int.from_bytes(d.read(4), "big") 80 | ctime = int.from_bytes(d.read(4), "big") 81 | size = int.from_bytes(d.read(8), "big") 82 | flags = int.from_bytes(d.read(1), "big") 83 | 84 | properties_count = int.from_bytes(d.read(1), "big") 85 | properties = [] 86 | 87 | for _ in range(properties_count): 88 | name_len = int.from_bytes(d.read(2), "big") 89 | name = d.read(name_len).decode("utf-8") if name_len != 0xffff else "" 90 | 91 | value_len = int.from_bytes(d.read(2), "big") 92 | value = d.read(value_len).decode("utf-8") if value_len != 0xffff else "" 93 | 94 | properties.append((name, value)) 95 | 96 | return cls(domain, filename, link, hash, key, mode, inode, user_id, group_id, mtime, atime, ctime, size, flags, properties) 97 | 98 | def to_bytes(self) -> bytes: 99 | d = BytesIO() 100 | 101 | d.write(len(self.domain).to_bytes(2, "big")) 102 | d.write(self.domain.encode("utf-8")) 103 | 104 | d.write(len(self.filename).to_bytes(2, "big")) 105 | d.write(self.filename.encode("utf-8")) 106 | 107 | d.write(len(self.link).to_bytes(2, "big")) 108 | d.write(self.link.encode("utf-8")) 109 | 110 | d.write(len(self.hash).to_bytes(2, "big")) 111 | d.write(self.hash) 112 | 113 | d.write(len(self.key).to_bytes(2, "big")) 114 | d.write(self.key) 115 | 116 | d.write(self.mode.to_bytes(2, "big")) 117 | #d.write(self.unknown2.to_bytes(4, "big")) 118 | #d.write(self.unknown3.to_bytes(4, "big")) 119 | d.write(self.inode.to_bytes(8, "big")) 120 | d.write(self.user_id.to_bytes(4, "big")) 121 | d.write(self.group_id.to_bytes(4, "big")) 122 | d.write(self.mtime.to_bytes(4, "big")) 123 | d.write(self.atime.to_bytes(4, "big")) 124 | d.write(self.ctime.to_bytes(4, "big")) 125 | d.write(self.size.to_bytes(8, "big")) 126 | d.write(self.flags.to_bytes(1, "big")) 127 | 128 | d.write(len(self.properties).to_bytes(1, "big")) 129 | 130 | for name, value in self.properties: 131 | d.write(len(name).to_bytes(2, "big")) 132 | d.write(name.encode("utf-8")) 133 | 134 | d.write(len(value).to_bytes(2, "big")) 135 | d.write(value.encode("utf-8")) 136 | 137 | return d.getvalue() 138 | 139 | @dataclass 140 | class Mbdb: 141 | records: list[MbdbRecord] 142 | 143 | @classmethod 144 | def from_bytes(cls, data: bytes): 145 | d = BytesIO(data) 146 | 147 | if d.read(4) != b"mbdb": 148 | raise ValueError("Invalid MBDB file") 149 | 150 | if d.read(2) != b"\x05\x00": 151 | raise ValueError("Invalid MBDB version") 152 | 153 | records = [] 154 | while d.tell() < len(data): 155 | records.append(MbdbRecord.from_stream(d)) 156 | 157 | return cls(records) 158 | 159 | def to_bytes(self) -> bytes: 160 | d = BytesIO() 161 | 162 | d.write(b"mbdb") 163 | d.write(b"\x05\x00") 164 | 165 | for record in self.records: 166 | d.write(record.to_bytes()) 167 | 168 | return d.getvalue() -------------------------------------------------------------------------------- /exploit/restore.py: -------------------------------------------------------------------------------- 1 | from . import backup, perform_restore 2 | from pymobiledevice3.lockdown import LockdownClient 3 | 4 | class FileToRestore: 5 | def __init__(self, contents: str, restore_path: str, restore_name: str, owner: int = 501, group: int = 501): 6 | self.contents = contents 7 | self.restore_path = restore_path 8 | self.restore_name = restore_name 9 | self.owner = owner 10 | self.group = group 11 | 12 | # files is a list of FileToRestore objects 13 | def restore_files(files: list, reboot: bool = False, lockdown_client: LockdownClient = None): 14 | # create the files to be backed up 15 | files_list = [ 16 | backup.Directory("", "RootDomain"), 17 | backup.Directory("Library", "RootDomain"), 18 | backup.Directory("Library/Preferences", "RootDomain"), 19 | ] 20 | # create the links 21 | for file_num in range(len(files)): 22 | files_list.append(backup.ConcreteFile( 23 | f"Library/Preferences/temp{file_num}", 24 | "RootDomain", 25 | owner=files[file_num].owner, 26 | group=files[file_num].group, 27 | contents=files[file_num].contents, 28 | inode=file_num 29 | )) 30 | # add the file paths 31 | for file_num in range(len(files)): 32 | file = files[file_num] 33 | base_path = "/var/backup" 34 | # set it to work in the separate volumes (prevents a bootloop) 35 | if file.restore_path.startswith("/var/mobile/"): 36 | # required on iOS 17.0+ since /var/mobile is on a separate partition 37 | base_path = "/var/mobile/backup" 38 | elif file.restore_path.startswith("/private/var/mobile/"): 39 | base_path = "/private/var/mobile/backup" 40 | elif file.restore_path.startswith("/private/var/"): 41 | base_path = "/private/var/backup" 42 | files_list.append(backup.Directory( 43 | "", 44 | f"SysContainerDomain-../../../../../../../..{base_path}{file.restore_path}", 45 | owner=file.owner, 46 | group=file.group 47 | )) 48 | files_list.append(backup.ConcreteFile( 49 | "", 50 | f"SysContainerDomain-../../../../../../../..{base_path}{file.restore_path}{file.restore_name}", 51 | owner=file.owner, 52 | group=file.group, 53 | contents=b"", 54 | inode=file_num 55 | )) 56 | # break the hard links 57 | for file_num in range(len(files)): 58 | files_list.append(backup.ConcreteFile( 59 | "", 60 | f"SysContainerDomain-../../../../../../../../var/.backup.i/var/root/Library/Preferences/temp{file_num}", 61 | owner=501, 62 | group=501, 63 | contents=b"", 64 | )) # Break the hard link 65 | files_list.append(backup.ConcreteFile("", "SysContainerDomain-../../../../../../../.." + "/crash_on_purpose", contents=b"")) 66 | 67 | # create the backup 68 | back = backup.Backup(files=files_list) 69 | 70 | perform_restore(backup=back, reboot=reboot, lockdown_client=lockdown_client) 71 | 72 | 73 | def restore_file(fp: str, restore_path: str, restore_name: str, reboot: bool = False, lockdown_client: LockdownClient = None): 74 | # open the file and read the contents 75 | contents = open(fp, "rb").read() 76 | 77 | base_path = "/var/backup" 78 | if restore_path.startswith("/var/mobile/"): 79 | # required on iOS 17.0+ since /var/mobile is on a separate partition 80 | base_path = "/var/mobile/backup" 81 | 82 | # create the backup 83 | back = backup.Backup(files=[ 84 | backup.Directory("", "RootDomain"), 85 | backup.Directory("Library", "RootDomain"), 86 | backup.Directory("Library/Preferences", "RootDomain"), 87 | backup.ConcreteFile("Library/Preferences/temp", "RootDomain", owner=501, group=501, contents=contents, inode=0), 88 | backup.Directory( 89 | "", 90 | f"SysContainerDomain-../../../../../../../..{base_path}{restore_path}", 91 | owner=501, 92 | group=501 93 | ), 94 | backup.ConcreteFile( 95 | "", 96 | f"SysContainerDomain-../../../../../../../..{base_path}{restore_path}{restore_name}", 97 | owner=501, 98 | group=501, 99 | contents=b"", 100 | inode=0 101 | ), 102 | backup.ConcreteFile( 103 | "", 104 | "SysContainerDomain-../../../../../../../../var/.backup.i/var/root/Library/Preferences/temp", 105 | owner=501, 106 | group=501, 107 | contents=b"", 108 | ), # Break the hard link 109 | backup.ConcreteFile("", "SysContainerDomain-../../../../../../../.." + "/crash_on_purpose", contents=b""), 110 | ]) 111 | 112 | 113 | perform_restore(backup=back, reboot=reboot, lockdown_client=lockdown_client) -------------------------------------------------------------------------------- /tweak/__pycache__/eligibility.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nightfallenxyz/AIEnabler/215a192797199fe0a8f3ecd374eec9417e2b8d27/tweak/__pycache__/eligibility.cpython-312.pyc -------------------------------------------------------------------------------- /tweak/eligibility.py: -------------------------------------------------------------------------------- 1 | from exploit.restore import FileToRestore, restore_files 2 | import json 3 | from pathlib import Path 4 | from pymobiledevice3.services.installation_proxy import InstallationProxyService 5 | 6 | class EUTweak: 7 | def __init__(self, method="2"): 8 | print("Initializing...") 9 | self.files = [] 10 | self.set_method(method) 11 | 12 | def set_method(self, method): 13 | self.method = method 14 | 15 | def setup_variables(self, dev_manager): 16 | try: 17 | with open(Path.joinpath(Path.cwd(), 'tweak/files/restore.json'), 'r') as json_file: 18 | json_data = json.load(json_file) 19 | 20 | for file_info in json_data["restore_files"]: 21 | file_to_restore_empty = FileToRestore( 22 | contents=b'', 23 | restore_path=f"/{file_info['path']}", 24 | restore_name=file_info["file"] 25 | ) 26 | file_to_restore = FileToRestore( 27 | contents=open(Path.joinpath(Path.cwd(), f'tweak/files/{file_info["file"]}'), 'rb').read(), 28 | restore_path=f"/{file_info['path']}", 29 | restore_name=file_info["file"] 30 | ) 31 | self.files.append({ 32 | "file_to_restore": file_to_restore, 33 | "file_to_restore_empty": file_to_restore_empty, 34 | "file_info": file_info 35 | }) 36 | 37 | if not self.files: 38 | print("No valid files to restore.") 39 | 40 | except (FileNotFoundError, json.JSONDecodeError, ValueError) as e: 41 | print(f"Error during setup: {e}") 42 | 43 | def apply(self, dev_manager): 44 | self.setup_variables(dev_manager) 45 | for file_entry in self.files: 46 | file_to_restore = file_entry["file_to_restore"] 47 | file_info = file_entry["file_info"] 48 | print(f"Restoring {file_info.get('file')} to {file_info.get('path')}") 49 | restore_files([file_to_restore], reboot=False, lockdown_client=dev_manager.device.get("ld")) 50 | restore_files([], reboot=True, lockdown_client=dev_manager.device.get("ld")) 51 | -------------------------------------------------------------------------------- /tweak/files/eligibility.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OS_ELIGIBILITY_DOMAIN_CALCIUM 6 | 7 | os_eligibility_answer_source_t 8 | 1 9 | os_eligibility_answer_t 10 | 2 11 | status 12 | 13 | OS_ELIGIBILITY_INPUT_CHINA_CELLULAR 14 | 2 15 | 16 | 17 | OS_ELIGIBILITY_DOMAIN_GREYMATTER 18 | 19 | context 20 | 21 | OS_ELIGIBILITY_CONTEXT_ELIGIBLE_DEVICE_LANGUAGES 22 | 23 | en 24 | 25 | 26 | os_eligibility_answer_source_t 27 | 1 28 | os_eligibility_answer_t 29 | 4 30 | status 31 | 32 | OS_ELIGIBILITY_INPUT_DEVICE_LANGUAGE 33 | 3 34 | OS_ELIGIBILITY_INPUT_DEVICE_REGION_CODE 35 | 3 36 | OS_ELIGIBILITY_INPUT_EXTERNAL_BOOT_DRIVE 37 | 3 38 | OS_ELIGIBILITY_INPUT_GENERATIVE_MODEL_SYSTEM 39 | 3 40 | OS_ELIGIBILITY_INPUT_SHARED_IPAD 41 | 3 42 | OS_ELIGIBILITY_INPUT_SIRI_LANGUAGE 43 | 3 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tweak/files/restore.json: -------------------------------------------------------------------------------- 1 | { 2 | "restore_files": [ 3 | { 4 | "file": "com.apple.MobileGestalt.plist", 5 | "path": "/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/" 6 | }, 7 | { 8 | "file": "eligibility.plist", 9 | "path": "var/db/os_eligibility/" 10 | } 11 | ] 12 | } --------------------------------------------------------------------------------